rules.go 7.2 KB

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