github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/signer/rules/rules.go (about) 1 2 package rules 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "os" 8 "strings" 9 10 "github.com/quickchainproject/quickchain/common" 11 "github.com/quickchainproject/quickchain/internal/qctapi" 12 "github.com/quickchainproject/quickchain/log" 13 "github.com/quickchainproject/quickchain/signer/core" 14 "github.com/quickchainproject/quickchain/signer/rules/deps" 15 "github.com/quickchainproject/quickchain/signer/storage" 16 "github.com/robertkrimen/otto" 17 ) 18 19 var ( 20 BigNumber_JS = deps.MustAsset("bignumber.js") 21 ) 22 23 // consoleOutput is an override for the console.log and console.error methods to 24 // stream the output into the configured output stream instead of stdout. 25 func consoleOutput(call otto.FunctionCall) otto.Value { 26 output := []string{"JS:> "} 27 for _, argument := range call.ArgumentList { 28 output = append(output, fmt.Sprintf("%v", argument)) 29 } 30 fmt.Fprintln(os.Stdout, strings.Join(output, " ")) 31 return otto.Value{} 32 } 33 34 // rulesetUi provides an implementation of SignerUI that evaluates a javascript 35 // file for each defined UI-method 36 type rulesetUi struct { 37 next core.SignerUI // The next handler, for manual processing 38 storage storage.Storage 39 credentials storage.Storage 40 jsRules string // The rules to use 41 } 42 43 func NewRuleEvaluator(next core.SignerUI, jsbackend, credentialsBackend storage.Storage) (*rulesetUi, error) { 44 c := &rulesetUi{ 45 next: next, 46 storage: jsbackend, 47 credentials: credentialsBackend, 48 jsRules: "", 49 } 50 51 return c, nil 52 } 53 54 func (r *rulesetUi) Init(javascriptRules string) error { 55 r.jsRules = javascriptRules 56 return nil 57 } 58 func (r *rulesetUi) execute(jsfunc string, jsarg interface{}) (otto.Value, error) { 59 60 // Instantiate a fresh vm engine every time 61 vm := otto.New() 62 // Set the native callbacks 63 consoleObj, _ := vm.Get("console") 64 consoleObj.Object().Set("log", consoleOutput) 65 consoleObj.Object().Set("error", consoleOutput) 66 vm.Set("storage", r.storage) 67 68 // Load bootstrap libraries 69 script, err := vm.Compile("bignumber.js", BigNumber_JS) 70 if err != nil { 71 log.Warn("Failed loading libraries", "err", err) 72 return otto.UndefinedValue(), err 73 } 74 vm.Run(script) 75 76 // Run the actual rule implementation 77 _, err = vm.Run(r.jsRules) 78 if err != nil { 79 log.Warn("Execution failed", "err", err) 80 return otto.UndefinedValue(), err 81 } 82 83 // And the actual call 84 // All calls are objects with the parameters being keys in that object. 85 // To provide additional insulation between js and go, we serialize it into JSON on the Go-side, 86 // and deserialize it on the JS side. 87 88 jsonbytes, err := json.Marshal(jsarg) 89 if err != nil { 90 log.Warn("failed marshalling data", "data", jsarg) 91 return otto.UndefinedValue(), err 92 } 93 // Now, we call foobar(JSON.parse(<jsondata>)). 94 var call string 95 if len(jsonbytes) > 0 { 96 call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes)) 97 } else { 98 call = fmt.Sprintf("%v()", jsfunc) 99 } 100 return vm.Run(call) 101 } 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 127 func (r *rulesetUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 128 jsonreq, err := json.Marshal(request) 129 approved, err := r.checkApproval("ApproveTx", jsonreq, err) 130 if err != nil { 131 log.Info("Rule-based approval error, going to manual", "error", err) 132 return r.next.ApproveTx(request) 133 } 134 135 if approved { 136 return core.SignTxResponse{ 137 Transaction: request.Transaction, 138 Approved: true, 139 Password: r.lookupPassword(request.Transaction.From.Address()), 140 }, 141 nil 142 } 143 return core.SignTxResponse{Approved: false}, err 144 } 145 146 func (r *rulesetUi) lookupPassword(address common.Address) string { 147 return r.credentials.Get(strings.ToLower(address.String())) 148 } 149 150 func (r *rulesetUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 151 jsonreq, err := json.Marshal(request) 152 approved, err := r.checkApproval("ApproveSignData", jsonreq, err) 153 if err != nil { 154 log.Info("Rule-based approval error, going to manual", "error", err) 155 return r.next.ApproveSignData(request) 156 } 157 if approved { 158 return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil 159 } 160 return core.SignDataResponse{Approved: false, Password: ""}, err 161 } 162 163 func (r *rulesetUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 164 jsonreq, err := json.Marshal(request) 165 approved, err := r.checkApproval("ApproveExport", jsonreq, err) 166 if err != nil { 167 log.Info("Rule-based approval error, going to manual", "error", err) 168 return r.next.ApproveExport(request) 169 } 170 if approved { 171 return core.ExportResponse{Approved: true}, nil 172 } 173 return core.ExportResponse{Approved: false}, err 174 } 175 176 func (r *rulesetUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 177 // This cannot be handled by rules, requires setting a password 178 // dispatch to next 179 return r.next.ApproveImport(request) 180 } 181 182 func (r *rulesetUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 183 jsonreq, err := json.Marshal(request) 184 approved, err := r.checkApproval("ApproveListing", jsonreq, err) 185 if err != nil { 186 log.Info("Rule-based approval error, going to manual", "error", err) 187 return r.next.ApproveListing(request) 188 } 189 if approved { 190 return core.ListResponse{Accounts: request.Accounts}, nil 191 } 192 return core.ListResponse{}, err 193 } 194 195 func (r *rulesetUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 196 // This cannot be handled by rules, requires setting a password 197 // dispatch to next 198 return r.next.ApproveNewAccount(request) 199 } 200 201 func (r *rulesetUi) ShowError(message string) { 202 log.Error(message) 203 r.next.ShowError(message) 204 } 205 206 func (r *rulesetUi) ShowInfo(message string) { 207 log.Info(message) 208 r.next.ShowInfo(message) 209 } 210 func (r *rulesetUi) OnSignerStartup(info core.StartupInfo) { 211 jsonInfo, err := json.Marshal(info) 212 if err != nil { 213 log.Warn("failed marshalling data", "data", info) 214 return 215 } 216 r.next.OnSignerStartup(info) 217 _, err = r.execute("OnSignerStartup", string(jsonInfo)) 218 if err != nil { 219 log.Info("error occurred during execution", "error", err) 220 } 221 } 222 223 func (r *rulesetUi) OnApprovedTx(tx qctapi.SignTransactionResult) { 224 jsonTx, err := json.Marshal(tx) 225 if err != nil { 226 log.Warn("failed marshalling transaction", "tx", tx) 227 return 228 } 229 _, err = r.execute("OnApprovedTx", string(jsonTx)) 230 if err != nil { 231 log.Info("error occurred during execution", "error", err) 232 } 233 }