rules.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package rules
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "os"
  21. "strings"
  22. "github.com/ethereum/go-ethereum/common"
  23. "github.com/ethereum/go-ethereum/internal/ethapi"
  24. "github.com/ethereum/go-ethereum/log"
  25. "github.com/ethereum/go-ethereum/signer/core"
  26. "github.com/ethereum/go-ethereum/signer/rules/deps"
  27. "github.com/ethereum/go-ethereum/signer/storage"
  28. "github.com/robertkrimen/otto"
  29. )
  30. var (
  31. BigNumber_JS = deps.MustAsset("bignumber.js")
  32. )
  33. // consoleOutput is an override for the console.log and console.error methods to
  34. // stream the output into the configured output stream instead of stdout.
  35. func consoleOutput(call otto.FunctionCall) otto.Value {
  36. output := []string{"JS:> "}
  37. for _, argument := range call.ArgumentList {
  38. output = append(output, fmt.Sprintf("%v", argument))
  39. }
  40. fmt.Fprintln(os.Stdout, strings.Join(output, " "))
  41. return otto.Value{}
  42. }
  43. // rulesetUI provides an implementation of SignerUI that evaluates a javascript
  44. // file for each defined UI-method
  45. type rulesetUI struct {
  46. next core.SignerUI // The next handler, for manual processing
  47. storage storage.Storage
  48. credentials storage.Storage
  49. jsRules string // The rules to use
  50. }
  51. func NewRuleEvaluator(next core.SignerUI, jsbackend, credentialsBackend storage.Storage) (*rulesetUI, error) {
  52. c := &rulesetUI{
  53. next: next,
  54. storage: jsbackend,
  55. credentials: credentialsBackend,
  56. jsRules: "",
  57. }
  58. return c, nil
  59. }
  60. func (r *rulesetUI) Init(javascriptRules string) error {
  61. r.jsRules = javascriptRules
  62. return nil
  63. }
  64. func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (otto.Value, error) {
  65. // Instantiate a fresh vm engine every time
  66. vm := otto.New()
  67. // Set the native callbacks
  68. consoleObj, _ := vm.Get("console")
  69. consoleObj.Object().Set("log", consoleOutput)
  70. consoleObj.Object().Set("error", consoleOutput)
  71. vm.Set("storage", r.storage)
  72. // Load bootstrap libraries
  73. script, err := vm.Compile("bignumber.js", BigNumber_JS)
  74. if err != nil {
  75. log.Warn("Failed loading libraries", "err", err)
  76. return otto.UndefinedValue(), err
  77. }
  78. vm.Run(script)
  79. // Run the actual rule implementation
  80. _, err = vm.Run(r.jsRules)
  81. if err != nil {
  82. log.Warn("Execution failed", "err", err)
  83. return otto.UndefinedValue(), err
  84. }
  85. // And the actual call
  86. // All calls are objects with the parameters being keys in that object.
  87. // To provide additional insulation between js and go, we serialize it into JSON on the Go-side,
  88. // and deserialize it on the JS side.
  89. jsonbytes, err := json.Marshal(jsarg)
  90. if err != nil {
  91. log.Warn("failed marshalling data", "data", jsarg)
  92. return otto.UndefinedValue(), err
  93. }
  94. // Now, we call foobar(JSON.parse(<jsondata>)).
  95. var call string
  96. if len(jsonbytes) > 0 {
  97. call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes))
  98. } else {
  99. call = fmt.Sprintf("%v()", jsfunc)
  100. }
  101. return vm.Run(call)
  102. }
  103. func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, error) {
  104. if err != nil {
  105. return false, err
  106. }
  107. v, err := r.execute(jsfunc, string(jsarg))
  108. if err != nil {
  109. log.Info("error occurred during execution", "error", err)
  110. return false, err
  111. }
  112. result, err := v.ToString()
  113. if err != nil {
  114. log.Info("error occurred during response unmarshalling", "error", err)
  115. return false, err
  116. }
  117. if result == "Approve" {
  118. log.Info("Op approved")
  119. return true, nil
  120. } else if result == "Reject" {
  121. log.Info("Op rejected")
  122. return false, nil
  123. }
  124. return false, fmt.Errorf("Unknown response")
  125. }
  126. func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  127. jsonreq, err := json.Marshal(request)
  128. approved, err := r.checkApproval("ApproveTx", jsonreq, err)
  129. if err != nil {
  130. log.Info("Rule-based approval error, going to manual", "error", err)
  131. return r.next.ApproveTx(request)
  132. }
  133. if approved {
  134. return core.SignTxResponse{
  135. Transaction: request.Transaction,
  136. Approved: true,
  137. Password: r.lookupPassword(request.Transaction.From.Address()),
  138. },
  139. nil
  140. }
  141. return core.SignTxResponse{Approved: false}, err
  142. }
  143. func (r *rulesetUI) lookupPassword(address common.Address) string {
  144. return r.credentials.Get(strings.ToLower(address.String()))
  145. }
  146. func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  147. jsonreq, err := json.Marshal(request)
  148. approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
  149. if err != nil {
  150. log.Info("Rule-based approval error, going to manual", "error", err)
  151. return r.next.ApproveSignData(request)
  152. }
  153. if approved {
  154. return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil
  155. }
  156. return core.SignDataResponse{Approved: false, Password: ""}, err
  157. }
  158. func (r *rulesetUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
  159. jsonreq, err := json.Marshal(request)
  160. approved, err := r.checkApproval("ApproveExport", jsonreq, err)
  161. if err != nil {
  162. log.Info("Rule-based approval error, going to manual", "error", err)
  163. return r.next.ApproveExport(request)
  164. }
  165. if approved {
  166. return core.ExportResponse{Approved: true}, nil
  167. }
  168. return core.ExportResponse{Approved: false}, err
  169. }
  170. func (r *rulesetUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
  171. // This cannot be handled by rules, requires setting a password
  172. // dispatch to next
  173. return r.next.ApproveImport(request)
  174. }
  175. func (r *rulesetUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  176. jsonreq, err := json.Marshal(request)
  177. approved, err := r.checkApproval("ApproveListing", jsonreq, err)
  178. if err != nil {
  179. log.Info("Rule-based approval error, going to manual", "error", err)
  180. return r.next.ApproveListing(request)
  181. }
  182. if approved {
  183. return core.ListResponse{Accounts: request.Accounts}, nil
  184. }
  185. return core.ListResponse{}, err
  186. }
  187. func (r *rulesetUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  188. // This cannot be handled by rules, requires setting a password
  189. // dispatch to next
  190. return r.next.ApproveNewAccount(request)
  191. }
  192. func (r *rulesetUI) ShowError(message string) {
  193. log.Error(message)
  194. r.next.ShowError(message)
  195. }
  196. func (r *rulesetUI) ShowInfo(message string) {
  197. log.Info(message)
  198. r.next.ShowInfo(message)
  199. }
  200. func (r *rulesetUI) OnSignerStartup(info core.StartupInfo) {
  201. jsonInfo, err := json.Marshal(info)
  202. if err != nil {
  203. log.Warn("failed marshalling data", "data", info)
  204. return
  205. }
  206. r.next.OnSignerStartup(info)
  207. _, err = r.execute("OnSignerStartup", string(jsonInfo))
  208. if err != nil {
  209. log.Info("error occurred during execution", "error", err)
  210. }
  211. }
  212. func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
  213. jsonTx, err := json.Marshal(tx)
  214. if err != nil {
  215. log.Warn("failed marshalling transaction", "tx", tx)
  216. return
  217. }
  218. _, err = r.execute("OnApprovedTx", string(jsonTx))
  219. if err != nil {
  220. log.Info("error occurred during execution", "error", err)
  221. }
  222. }