rules.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser 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. // The go-ethereum library 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 Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. 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/dop251/goja"
  23. "github.com/ethereum/go-ethereum/internal/ethapi"
  24. "github.com/ethereum/go-ethereum/internal/jsre/deps"
  25. "github.com/ethereum/go-ethereum/log"
  26. "github.com/ethereum/go-ethereum/signer/core"
  27. "github.com/ethereum/go-ethereum/signer/storage"
  28. )
  29. // consoleOutput is an override for the console.log and console.error methods to
  30. // stream the output into the configured output stream instead of stdout.
  31. func consoleOutput(call goja.FunctionCall) goja.Value {
  32. output := []string{"JS:> "}
  33. for _, argument := range call.Arguments {
  34. output = append(output, fmt.Sprintf("%v", argument))
  35. }
  36. fmt.Fprintln(os.Stderr, strings.Join(output, " "))
  37. return goja.Undefined()
  38. }
  39. // rulesetUI provides an implementation of UIClientAPI that evaluates a javascript
  40. // file for each defined UI-method
  41. type rulesetUI struct {
  42. next core.UIClientAPI // The next handler, for manual processing
  43. storage storage.Storage
  44. jsRules string // The rules to use
  45. }
  46. func NewRuleEvaluator(next core.UIClientAPI, jsbackend storage.Storage) (*rulesetUI, error) {
  47. c := &rulesetUI{
  48. next: next,
  49. storage: jsbackend,
  50. jsRules: "",
  51. }
  52. return c, nil
  53. }
  54. func (r *rulesetUI) RegisterUIServer(api *core.UIServerAPI) {
  55. r.next.RegisterUIServer(api)
  56. // TODO, make it possible to query from js
  57. }
  58. func (r *rulesetUI) Init(javascriptRules string) error {
  59. r.jsRules = javascriptRules
  60. return nil
  61. }
  62. func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (goja.Value, error) {
  63. // Instantiate a fresh vm engine every time
  64. vm := goja.New()
  65. // Set the native callbacks
  66. consoleObj := vm.NewObject()
  67. consoleObj.Set("log", consoleOutput)
  68. consoleObj.Set("error", consoleOutput)
  69. vm.Set("console", consoleObj)
  70. storageObj := vm.NewObject()
  71. storageObj.Set("put", func(call goja.FunctionCall) goja.Value {
  72. key, val := call.Argument(0).String(), call.Argument(1).String()
  73. if val == "" {
  74. r.storage.Del(key)
  75. } else {
  76. r.storage.Put(key, val)
  77. }
  78. return goja.Null()
  79. })
  80. storageObj.Set("get", func(call goja.FunctionCall) goja.Value {
  81. goval, _ := r.storage.Get(call.Argument(0).String())
  82. jsval := vm.ToValue(goval)
  83. return jsval
  84. })
  85. vm.Set("storage", storageObj)
  86. // Load bootstrap libraries
  87. script, err := goja.Compile("bignumber.js", deps.BigNumberJS, true)
  88. if err != nil {
  89. log.Warn("Failed loading libraries", "err", err)
  90. return goja.Undefined(), err
  91. }
  92. vm.RunProgram(script)
  93. // Run the actual rule implementation
  94. _, err = vm.RunString(r.jsRules)
  95. if err != nil {
  96. log.Warn("Execution failed", "err", err)
  97. return goja.Undefined(), err
  98. }
  99. // And the actual call
  100. // All calls are objects with the parameters being keys in that object.
  101. // To provide additional insulation between js and go, we serialize it into JSON on the Go-side,
  102. // and deserialize it on the JS side.
  103. jsonbytes, err := json.Marshal(jsarg)
  104. if err != nil {
  105. log.Warn("failed marshalling data", "data", jsarg)
  106. return goja.Undefined(), err
  107. }
  108. // Now, we call foobar(JSON.parse(<jsondata>)).
  109. var call string
  110. if len(jsonbytes) > 0 {
  111. call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes))
  112. } else {
  113. call = fmt.Sprintf("%v()", jsfunc)
  114. }
  115. return vm.RunString(call)
  116. }
  117. func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, error) {
  118. if err != nil {
  119. return false, err
  120. }
  121. v, err := r.execute(jsfunc, string(jsarg))
  122. if err != nil {
  123. log.Info("error occurred during execution", "error", err)
  124. return false, err
  125. }
  126. result := v.ToString().String()
  127. if result == "Approve" {
  128. log.Info("Op approved")
  129. return true, nil
  130. } else if result == "Reject" {
  131. log.Info("Op rejected")
  132. return false, nil
  133. }
  134. return false, fmt.Errorf("unknown response")
  135. }
  136. func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  137. jsonreq, err := json.Marshal(request)
  138. approved, err := r.checkApproval("ApproveTx", jsonreq, err)
  139. if err != nil {
  140. log.Info("Rule-based approval error, going to manual", "error", err)
  141. return r.next.ApproveTx(request)
  142. }
  143. if approved {
  144. return core.SignTxResponse{
  145. Transaction: request.Transaction,
  146. Approved: true},
  147. nil
  148. }
  149. return core.SignTxResponse{Approved: false}, err
  150. }
  151. func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  152. jsonreq, err := json.Marshal(request)
  153. approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
  154. if err != nil {
  155. log.Info("Rule-based approval error, going to manual", "error", err)
  156. return r.next.ApproveSignData(request)
  157. }
  158. if approved {
  159. return core.SignDataResponse{Approved: true}, nil
  160. }
  161. return core.SignDataResponse{Approved: false}, err
  162. }
  163. // OnInputRequired not handled by rules
  164. func (r *rulesetUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
  165. return r.next.OnInputRequired(info)
  166. }
  167. func (r *rulesetUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  168. jsonreq, err := json.Marshal(request)
  169. approved, err := r.checkApproval("ApproveListing", jsonreq, err)
  170. if err != nil {
  171. log.Info("Rule-based approval error, going to manual", "error", err)
  172. return r.next.ApproveListing(request)
  173. }
  174. if approved {
  175. return core.ListResponse{Accounts: request.Accounts}, nil
  176. }
  177. return core.ListResponse{}, err
  178. }
  179. func (r *rulesetUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  180. // This cannot be handled by rules, requires setting a password
  181. // dispatch to next
  182. return r.next.ApproveNewAccount(request)
  183. }
  184. func (r *rulesetUI) ShowError(message string) {
  185. log.Error(message)
  186. r.next.ShowError(message)
  187. }
  188. func (r *rulesetUI) ShowInfo(message string) {
  189. log.Info(message)
  190. r.next.ShowInfo(message)
  191. }
  192. func (r *rulesetUI) OnSignerStartup(info core.StartupInfo) {
  193. jsonInfo, err := json.Marshal(info)
  194. if err != nil {
  195. log.Warn("failed marshalling data", "data", info)
  196. return
  197. }
  198. r.next.OnSignerStartup(info)
  199. _, err = r.execute("OnSignerStartup", string(jsonInfo))
  200. if err != nil {
  201. log.Info("error occurred during execution", "error", err)
  202. }
  203. }
  204. func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
  205. jsonTx, err := json.Marshal(tx)
  206. if err != nil {
  207. log.Warn("failed marshalling transaction", "tx", tx)
  208. return
  209. }
  210. _, err = r.execute("OnApprovedTx", string(jsonTx))
  211. if err != nil {
  212. log.Info("error occurred during execution", "error", err)
  213. }
  214. }