Commit 5f94f8c7 authored by Martin Holst Swende's avatar Martin Holst Swende Committed by Péter Szilágyi

signer: change the stdio jsonrpc to use legacy namespace conventions (#19047)

This PR will will break existing UIs, since it changes all calls like ApproveSignTransaction to be on the form ui_approveSignTransaction.

This is to make it possible for the UI to reuse the json-rpc library from go-ethereum, which uses this convention.

Also, this PR removes some unused structs, after import/export were removed from the external api (so no longer needs internal methods for approval)

One more breaking change is introduced, removing passwords from the ApproveSignTxResponse and the likes. This makes the manual interface more like the rulebased interface, and integrates nicely with the credential storage. Thus, the way it worked before, it would be tempting for the UI to implement 'remember password' functionality. The way it is now, it will be easy instead to tell clef to store passwords and use them.

If a pw is not found in the credential store, the user is prompted to provide the password.
parent eb199f1f
......@@ -661,7 +661,7 @@ OBS! A slight deviation from `json` standard is in place: every request and resp
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
things simpler for both parties.
### ApproveTx
### ApproveTx / `ui_approveTx`
Invoked when there's a transaction for approval.
......@@ -673,13 +673,13 @@ Here's a method invocation:
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
```
Results in the following invocation on the UI:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "ApproveTx",
"method": "ui_approveTx",
"params": [
{
"transaction": {
......@@ -724,7 +724,7 @@ curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","me
{
"jsonrpc": "2.0",
"id": 1,
"method": "ApproveTx",
"method": "ui_approveTx",
"params": [
{
"transaction": {
......@@ -767,7 +767,7 @@ One which has missing `to`, but with no `data`:
{
"jsonrpc": "2.0",
"id": 3,
"method": "ApproveTx",
"method": "ui_approveTx",
"params": [
{
"transaction": {
......@@ -796,33 +796,7 @@ One which has missing `to`, but with no `data`:
}
```
### ApproveExport
Invoked when a request to export an account has been made.
#### Sample call
```json
{
"jsonrpc": "2.0",
"id": 7,
"method": "ApproveExport",
"params": [
{
"address": "0x0000000000000000000000000000000000000000",
"meta": {
"remote": "signer binary",
"local": "main",
"scheme": "in-proc"
}
}
]
}
```
### ApproveListing
### ApproveListing / `ui_approveListing`
Invoked when a request for account listing has been made.
......@@ -833,7 +807,7 @@ Invoked when a request for account listing has been made.
{
"jsonrpc": "2.0",
"id": 5,
"method": "ApproveListing",
"method": "ui_approveListing",
"params": [
{
"accounts": [
......@@ -860,7 +834,7 @@ Invoked when a request for account listing has been made.
```
### ApproveSignData
### ApproveSignData / `ui_approveSignData`
#### Sample call
......@@ -868,7 +842,7 @@ Invoked when a request for account listing has been made.
{
"jsonrpc": "2.0",
"id": 4,
"method": "ApproveSignData",
"method": "ui_approveSignData",
"params": [
{
"address": "0x123409812340981234098123409812deadbeef42",
......@@ -886,7 +860,7 @@ Invoked when a request for account listing has been made.
```
### ShowInfo
### ShowInfo / `ui_showInfo`
The UI should show the info to the user. Does not expect response.
......@@ -896,7 +870,7 @@ The UI should show the info to the user. Does not expect response.
{
"jsonrpc": "2.0",
"id": 9,
"method": "ShowInfo",
"method": "ui_showInfo",
"params": [
{
"text": "Tests completed"
......@@ -906,7 +880,7 @@ The UI should show the info to the user. Does not expect response.
```
### ShowError
### ShowError / `ui_showError`
The UI should show the info to the user. Does not expect response.
......@@ -925,7 +899,7 @@ The UI should show the info to the user. Does not expect response.
```
### OnApproved
### OnApprovedTx / `ui_onApprovedTx`
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
......@@ -933,7 +907,7 @@ When implementing rate-limited rules, this callback should be used.
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
### OnSignerStartup
### OnSignerStartup / `ui_onSignerStartup`
This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api,
in k/v-form.
......@@ -944,7 +918,7 @@ Example call:
{
"jsonrpc": "2.0",
"id": 1,
"method": "OnSignerStartup",
"method": "ui_onSignerStartup",
"params": [
{
"info": {
......
......@@ -35,8 +35,7 @@ Response to SignDataRequest
Example:
```json
{
"approved": true,
"Password": "apassword"
"approved": true
}
```
### SignDataResponse - deny
......@@ -46,8 +45,7 @@ Response to SignDataRequest
Example:
```json
{
"approved": false,
"Password": ""
"approved": false
}
```
### SignTxRequest
......@@ -89,9 +87,9 @@ Example:
}
}
```
### SignDataResponse - approve
### SignTxResponse - approve
Response to SignDataRequest. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
Response to request to sign a transaction. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
Example:
```json
......@@ -105,19 +103,26 @@ Example:
"nonce": "0x4",
"data": "0x04030201"
},
"approved": true,
"password": "apassword"
"approved": true
}
```
### SignDataResponse - deny
### SignTxResponse - deny
Response to SignDataRequest. When denying a request, there's no need to provide the transaction in return
Response to SignTxRequest. When denying a request, there's no need to provide the transaction in return
Example:
```json
{
"approved": false,
"Password": ""
"transaction": {
"from": "0x",
"to": null,
"gas": "0x0",
"gasPrice": "0x0",
"value": "0x0",
"nonce": "0x0",
"data": null
},
"approved": false
}
```
### OnApproved - SignTransactionResult
......@@ -164,7 +169,7 @@ Example:
```
### UserInputResponse
Response to SignDataRequest
Response to UserInputRequest
Example:
```json
......@@ -198,7 +203,7 @@ Example:
}
}
```
### UserInputResponse
### ListResponse
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not
......
### Changelog for internal API (ui-api)
### 6.0.0
Removed `password` from responses to operations which require them. This is for two reasons,
- Consistency between how rulesets operate and how manual processing works. A rule can `Approve` but require the actual password to be stored in the clef storage.
With this change, the same stored password can be used even if rulesets are not enabled, but storage is.
- It also removes the usability-shortcut that a UI might otherwise want to implement; remembering passwords. Since we now will not require the
password on every `Approve`, there's no need for the UI to cache it locally.
- In a future update, we'll likely add `clef_storePassword` to the internal API, so the user can store it via his UI (currently only CLI works).
Affected datatypes:
- `SignTxResponse`
- `SignDataResponse`
- `NewAccountResponse`
If `clef` requires a password, the `OnInputRequired` will be used to collect it.
### 5.0.0
Changed the namespace format to adhere to the legacy ethereum format: `name_methodName`. Changes:
* `ApproveTx` -> `ui_approveTx`
* `ApproveSignData` -> `ui_approveSignData`
* `ApproveExport` -> `removed`
* `ApproveImport` -> `removed`
* `ApproveListing` -> `ui_approveListing`
* `ApproveNewAccount` -> `ui_approveNewAccount`
* `ShowError` -> `ui_showError`
* `ShowInfo` -> `ui_showInfo`
* `OnApprovedTx` -> `ui_onApprovedTx`
* `OnSignerStartup` -> `ui_onSignerStartup`
* `OnInputRequired` -> `ui_onInputRequired`
### 4.0.0
* Bidirectional communication implemented, so the UI can query `clef` via the stdin/stdout RPC channel. Methods implemented are:
......
This diff is collapsed.
......@@ -23,6 +23,7 @@ import (
"fmt"
"math/big"
"reflect"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
......@@ -32,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/signer/storage"
)
const (
......@@ -40,7 +42,7 @@ const (
// ExternalAPIVersion -- see extapi_changelog.md
ExternalAPIVersion = "6.0.0"
// InternalAPIVersion -- see intapi_changelog.md
InternalAPIVersion = "4.0.0"
InternalAPIVersion = "6s.0.0"
)
// ExternalAPI defines the external API through which signing requests are made.
......@@ -68,10 +70,6 @@ type UIClientAPI interface {
ApproveTx(request *SignTxRequest) (SignTxResponse, error)
// ApproveSignData prompt the user for confirmation to request to sign data
ApproveSignData(request *SignDataRequest) (SignDataResponse, error)
// ApproveExport prompt the user for confirmation to export encrypted Account json
ApproveExport(request *ExportRequest) (ExportResponse, error)
// ApproveImport prompt the user for confirmation to import Account json
ApproveImport(request *ImportRequest) (ImportResponse, error)
// ApproveListing prompt the user for confirmation to list accounts
// the list of accounts to list can be modified by the UI
ApproveListing(request *ListRequest) (ListResponse, error)
......@@ -96,11 +94,12 @@ type UIClientAPI interface {
// SignerAPI defines the actual implementation of ExternalAPI
type SignerAPI struct {
chainID *big.Int
am *accounts.Manager
UI UIClientAPI
validator *Validator
rejectMode bool
chainID *big.Int
am *accounts.Manager
UI UIClientAPI
validator *Validator
rejectMode bool
credentials storage.Storage
}
// Metadata about a request
......@@ -187,25 +186,6 @@ type (
//The UI may make changes to the TX
Transaction SendTxArgs `json:"transaction"`
Approved bool `json:"approved"`
Password string `json:"password"`
}
// ExportRequest info about query to export accounts
ExportRequest struct {
Address common.Address `json:"address"`
Meta Metadata `json:"meta"`
}
// ExportResponse response to export-request
ExportResponse struct {
Approved bool `json:"approved"`
}
// ImportRequest info about request to import an Account
ImportRequest struct {
Meta Metadata `json:"meta"`
}
ImportResponse struct {
Approved bool `json:"approved"`
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
SignDataRequest struct {
ContentType string `json:"content_type"`
......@@ -217,14 +197,12 @@ type (
}
SignDataResponse struct {
Approved bool `json:"approved"`
Password string
}
NewAccountRequest struct {
Meta Metadata `json:"meta"`
}
NewAccountResponse struct {
Approved bool `json:"approved"`
Password string `json:"password"`
Approved bool `json:"approved"`
}
ListRequest struct {
Accounts []accounts.Account `json:"accounts"`
......@@ -240,8 +218,8 @@ type (
Info map[string]interface{} `json:"info"`
}
UserInputRequest struct {
Prompt string `json:"prompt"`
Title string `json:"title"`
Prompt string `json:"prompt"`
IsPassword bool `json:"isPassword"`
}
UserInputResponse struct {
......@@ -256,11 +234,11 @@ var ErrRequestDenied = errors.New("Request denied")
// key that is generated when a new Account is created.
// noUSB disables USB support that is required to support hardware devices such as
// ledger and trezor.
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool) *SignerAPI {
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool, credentials storage.Storage) *SignerAPI {
if advancedMode {
log.Info("Clef is in advanced mode: will warn instead of reject")
}
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode}
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode, credentials}
if !noUSB {
signer.startUSBListener()
}
......@@ -381,24 +359,27 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
if len(be) == 0 {
return common.Address{}, errors.New("password based accounts not supported")
}
var (
resp NewAccountResponse
err error
)
if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil {
return common.Address{}, err
} else if !resp.Approved {
return common.Address{}, ErrRequestDenied
}
// Three retries to get a valid password
for i := 0; i < 3; i++ {
resp, err = api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)})
resp, err := api.UI.OnInputRequired(UserInputRequest{
"New account password",
fmt.Sprintf("Please enter a password for the new account to be created (attempt %d of 3)", i),
true})
if err != nil {
return common.Address{}, err
}
if !resp.Approved {
return common.Address{}, ErrRequestDenied
log.Warn("error obtaining password", "attempt", i, "error", err)
continue
}
if pwErr := ValidatePasswordFormat(resp.Password); pwErr != nil {
if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil {
api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
} else {
// No error
acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Password)
acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text)
return acc.Address, err
}
}
......@@ -452,6 +433,24 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
return modified
}
func (api *SignerAPI) lookupPassword(address common.Address) string {
return api.credentials.Get(strings.ToLower(address.String()))
}
func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, prompt string) (string, error) {
if pw := api.lookupPassword(address); pw != "" {
return pw, nil
} else {
pwResp, err := api.UI.OnInputRequired(UserInputRequest{title, prompt, true})
if err != nil {
log.Warn("error obtaining password", "error", err)
// We'll not forward the error here, in case the error contains info about the response from the UI,
// which could leak the password if it was malformed json or something
return "", errors.New("internal error")
}
return pwResp.Text, nil
}
}
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
var (
......@@ -495,9 +494,14 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
}
// Convert fields into a real transaction
var unsignedTx = result.Transaction.toTransaction()
// Get the password for the transaction
pw, err := api.lookupOrQueryPassword(acc.Address, "Account password",
fmt.Sprintf("Please enter the password for account %s", acc.Address.String()))
if err != nil {
return nil, err
}
// The one to sign is the one that was returned from the UI
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID)
signedTx, err := wallet.SignTxWithPassphrase(acc, pw, unsignedTx, api.chainID)
if err != nil {
api.UI.ShowError(err.Error())
return nil, err
......
This diff is collapsed.
......@@ -87,9 +87,10 @@ func (ui *CommandlineUI) readPasswordText(inputstring string) string {
}
func (ui *CommandlineUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
fmt.Println(info.Title)
fmt.Println(info.Prompt)
fmt.Printf("## %s\n\n%s\n", info.Title, info.Prompt)
if info.IsPassword {
fmt.Printf("> ")
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
log.Error("Failed to read password", "err", err)
......@@ -156,9 +157,9 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
showMetadata(request.Meta)
fmt.Printf("-------------------------------------------\n")
if !ui.confirm() {
return SignTxResponse{request.Transaction, false, ""}, nil
return SignTxResponse{request.Transaction, false}, nil
}
return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil
return SignTxResponse{request.Transaction, true}, nil
}
// ApproveSignData prompt the user for confirmation to request to sign data
......@@ -178,40 +179,9 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta)
if !ui.confirm() {
return SignDataResponse{false, ""}, nil
}
return SignDataResponse{true, ui.readPassword()}, nil
}
// ApproveExport prompt the user for confirmation to export encrypted Account json
func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
ui.mu.Lock()
defer ui.mu.Unlock()
fmt.Printf("-------- Export Account request--------------\n")
fmt.Printf("A request has been made to export the (encrypted) keyfile\n")
fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n")
fmt.Printf("\n")
fmt.Printf("Account: %x\n", request.Address)
//fmt.Printf("keyfile: \n%v\n", request.file)
fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta)
return ExportResponse{ui.confirm()}, nil
}
// ApproveImport prompt the user for confirmation to import Account json
func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
ui.mu.Lock()
defer ui.mu.Unlock()
fmt.Printf("-------- Import Account request--------------\n")
fmt.Printf("A request has been made to import an encrypted keyfile\n")
fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta)
if !ui.confirm() {
return ImportResponse{false, "", ""}, nil
return SignDataResponse{false}, nil
}
return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil
return SignDataResponse{true}, nil
}
// ApproveListing prompt the user for confirmation to list accounts
......@@ -248,21 +218,20 @@ func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccou
fmt.Printf("and the address is returned to the external caller\n\n")
showMetadata(request.Meta)
if !ui.confirm() {
return NewAccountResponse{false, ""}, nil
return NewAccountResponse{false}, nil
}
return NewAccountResponse{true, ui.readPassword()}, nil
return NewAccountResponse{true}, nil
}
// ShowError displays error message to user
func (ui *CommandlineUI) ShowError(message string) {
fmt.Printf("-------- Error message from Clef-----------\n")
fmt.Println(message)
fmt.Printf("## Error \n%s\n", message)
fmt.Printf("-------------------------------------------\n")
}
// ShowInfo displays info message to user
func (ui *CommandlineUI) ShowInfo(message string) {
fmt.Printf("Info: %v\n", message)
fmt.Printf("## Info \n%s\n", message)
}
func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
......
......@@ -139,8 +139,14 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l
if err != nil {
return nil, err
}
pw, err := api.lookupOrQueryPassword(account.Address,
"Password for signing",
fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex()))
if err != nil {
return nil, err
}
// Sign the data with the wallet
signature, err := wallet.SignDataWithPassphrase(account, res.Password, req.ContentType, req.Rawdata)
signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata)
if err != nil {
return nil, err
}
......
......@@ -179,15 +179,15 @@ func TestSignData(t *testing.T) {
//Create two accounts
createAccount(control, api, t)
createAccount(control, api, t)
control <- "1"
control.approveCh <- "1"
list, err := api.List(context.Background())
if err != nil {
t.Fatal(err)
}
a := common.NewMixedcaseAddress(list[0])
control <- "Y"
control <- "wrongpassword"
control.approveCh <- "Y"
control.inputCh <- "wrongpassword"
signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil {
t.Errorf("Expected nil-data, got %x", signature)
......@@ -195,7 +195,7 @@ func TestSignData(t *testing.T) {
if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! '%v'", err)
}
control <- "No way"
control.approveCh <- "No way"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil {
t.Errorf("Expected nil-data, got %x", signature)
......@@ -204,8 +204,8 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected ErrRequestDenied! '%v'", err)
}
// text/plain
control <- "Y"
control <- "a_long_password"
control.approveCh <- "Y"
control.inputCh <- "a_long_password"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if err != nil {
t.Fatal(err)
......@@ -214,8 +214,8 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
}
// data/typed
control <- "Y"
control <- "a_long_password"
control.approveCh <- "Y"
control.inputCh <- "a_long_password"
signature, err = api.SignTypedData(context.Background(), a, typedData)
if err != nil {
t.Fatal(err)
......
......@@ -65,71 +65,59 @@ func (ui *StdIOUI) notify(serviceMethod string, args interface{}) error {
func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
var result SignTxResponse
err := ui.dispatch("ApproveTx", request, &result)
err := ui.dispatch("ui_approveTx", request, &result)
return result, err
}
func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
var result SignDataResponse
err := ui.dispatch("ApproveSignData", request, &result)
return result, err
}
func (ui *StdIOUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
var result ExportResponse
err := ui.dispatch("ApproveExport", request, &result)
return result, err
}
func (ui *StdIOUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
var result ImportResponse
err := ui.dispatch("ApproveImport", request, &result)
err := ui.dispatch("ui_approveSignData", request, &result)
return result, err
}
func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) {
var result ListResponse
err := ui.dispatch("ApproveListing", request, &result)
err := ui.dispatch("ui_approveListing", request, &result)
return result, err
}
func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
var result NewAccountResponse
err := ui.dispatch("ApproveNewAccount", request, &result)
err := ui.dispatch("ui_approveNewAccount", request, &result)
return result, err
}
func (ui *StdIOUI) ShowError(message string) {
err := ui.notify("ShowError", &Message{message})
err := ui.notify("ui_showError", &Message{message})
if err != nil {
log.Info("Error calling 'ShowError'", "exc", err.Error(), "msg", message)
log.Info("Error calling 'ui_showError'", "exc", err.Error(), "msg", message)
}
}
func (ui *StdIOUI) ShowInfo(message string) {
err := ui.notify("ShowInfo", Message{message})
err := ui.notify("ui_showInfo", Message{message})
if err != nil {
log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message)
log.Info("Error calling 'ui_showInfo'", "exc", err.Error(), "msg", message)
}
}
func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
err := ui.notify("OnApprovedTx", tx)
err := ui.notify("ui_onApprovedTx", tx)
if err != nil {
log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx)
log.Info("Error calling 'ui_onApprovedTx'", "exc", err.Error(), "tx", tx)
}
}
func (ui *StdIOUI) OnSignerStartup(info StartupInfo) {
err := ui.notify("OnSignerStartup", info)
err := ui.notify("ui_onSignerStartup", info)
if err != nil {
log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info)
log.Info("Error calling 'ui_onSignerStartup'", "exc", err.Error(), "info", info)
}
}
func (ui *StdIOUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
var result UserInputResponse
err := ui.dispatch("OnInputRequired", info, &result)
err := ui.dispatch("ui_onInputRequired", info, &result)
if err != nil {
log.Info("Error calling 'OnInputRequired'", "exc", err.Error(), "info", info)
log.Info("Error calling 'ui_onInputRequired'", "exc", err.Error(), "info", info)
}
return result, err
}
......@@ -22,7 +22,6 @@ import (
"os"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/signer/core"
......@@ -42,25 +41,23 @@ func consoleOutput(call otto.FunctionCall) otto.Value {
for _, argument := range call.ArgumentList {
output = append(output, fmt.Sprintf("%v", argument))
}
fmt.Fprintln(os.Stdout, strings.Join(output, " "))
fmt.Fprintln(os.Stderr, strings.Join(output, " "))
return otto.Value{}
}
// rulesetUI provides an implementation of UIClientAPI that evaluates a javascript
// file for each defined UI-method
type rulesetUI struct {
next core.UIClientAPI // The next handler, for manual processing
storage storage.Storage
credentials storage.Storage
jsRules string // The rules to use
next core.UIClientAPI // The next handler, for manual processing
storage storage.Storage
jsRules string // The rules to use
}
func NewRuleEvaluator(next core.UIClientAPI, jsbackend, credentialsBackend storage.Storage) (*rulesetUI, error) {
func NewRuleEvaluator(next core.UIClientAPI, jsbackend storage.Storage) (*rulesetUI, error) {
c := &rulesetUI{
next: next,
storage: jsbackend,
credentials: credentialsBackend,
jsRules: "",
next: next,
storage: jsbackend,
jsRules: "",
}
return c, nil
......@@ -153,18 +150,12 @@ func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse,
if approved {
return core.SignTxResponse{
Transaction: request.Transaction,
Approved: true,
Password: r.lookupPassword(request.Transaction.From.Address()),
},
Approved: true},
nil
}
return core.SignTxResponse{Approved: false}, err
}
func (r *rulesetUI) lookupPassword(address common.Address) string {
return r.credentials.Get(strings.ToLower(address.String()))
}
func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
jsonreq, err := json.Marshal(request)
approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
......@@ -173,28 +164,9 @@ func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDat
return r.next.ApproveSignData(request)
}
if approved {
return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil
}
return core.SignDataResponse{Approved: false, Password: ""}, err
}
func (r *rulesetUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
jsonreq, err := json.Marshal(request)
approved, err := r.checkApproval("ApproveExport", jsonreq, err)
if err != nil {
log.Info("Rule-based approval error, going to manual", "error", err)
return r.next.ApproveExport(request)
}
if approved {
return core.ExportResponse{Approved: true}, nil
return core.SignDataResponse{Approved: true}, nil
}
return core.ExportResponse{Approved: false}, err
}
func (r *rulesetUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
// This cannot be handled by rules, requires setting a password
// dispatch to next
return r.next.ApproveImport(request)
return core.SignDataResponse{Approved: false}, err
}
// OnInputRequired not handled by rules
......
......@@ -84,19 +84,11 @@ func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
}
func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
return core.SignTxResponse{Transaction: request.Transaction, Approved: false}, nil
}
func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
return core.SignDataResponse{Approved: false, Password: ""}, nil
}
func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
return core.ExportResponse{Approved: false}, nil
}
func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
return core.SignDataResponse{Approved: false}, nil
}
func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
......@@ -104,7 +96,7 @@ func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse
}
func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
return core.NewAccountResponse{Approved: false, Password: ""}, nil
return core.NewAccountResponse{Approved: false}, nil
}
func (alwaysDenyUI) ShowError(message string) {
......@@ -120,7 +112,7 @@ func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
}
func initRuleEngine(js string) (*rulesetUI, error) {
r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage())
if err != nil {
return nil, fmt.Errorf("failed to create js engine: %v", err)
}
......@@ -225,16 +217,6 @@ func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataR
return core.SignDataResponse{}, core.ErrRequestDenied
}
func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
d.calls = append(d.calls, "ApproveExport")
return core.ExportResponse{}, core.ErrRequestDenied
}
func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
d.calls = append(d.calls, "ApproveImport")
return core.ImportResponse{}, core.ErrRequestDenied
}
func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
d.calls = append(d.calls, "ApproveListing")
return core.ListResponse{}, core.ErrRequestDenied
......@@ -266,8 +248,7 @@ func TestForwarding(t *testing.T) {
js := ""
ui := &dummyUI{make([]string, 0)}
jsBackend := storage.NewEphemeralStorage()
credBackend := storage.NewEphemeralStorage()
r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
r, err := NewRuleEvaluator(ui, jsBackend)
if err != nil {
t.Fatalf("Failed to create js engine: %v", err)
}
......@@ -276,17 +257,15 @@ func TestForwarding(t *testing.T) {
}
r.ApproveSignData(nil)
r.ApproveTx(nil)
r.ApproveImport(nil)
r.ApproveNewAccount(nil)
r.ApproveListing(nil)
r.ApproveExport(nil)
r.ShowError("test")
r.ShowInfo("test")
//This one is not forwarded
r.OnApprovedTx(ethapi.SignTransactionResult{})
expCalls := 8
expCalls := 6
if len(ui.calls) != expCalls {
t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
......@@ -545,16 +524,6 @@ func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDa
return core.SignDataResponse{}, core.ErrRequestDenied
}
func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
d.t.Fatalf("Did not expect next-handler to be called")
return core.ExportResponse{}, core.ErrRequestDenied
}
func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
d.t.Fatalf("Did not expect next-handler to be called")
return core.ImportResponse{}, core.ErrRequestDenied
}
func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
d.t.Fatalf("Did not expect next-handler to be called")
return core.ListResponse{}, core.ErrRequestDenied
......@@ -597,7 +566,7 @@ func TestContextIsCleared(t *testing.T) {
}
`
ui := &dontCallMe{t}
r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage())
if err != nil {
t.Fatalf("Failed to create js engine: %v", err)
}
......
......@@ -17,10 +17,6 @@
package storage
import (
"fmt"
)
type Storage interface {
// Put stores a value by key. 0-length keys results in no-op
Put(key, value string)
......@@ -39,7 +35,7 @@ func (s *EphemeralStorage) Put(key, value string) {
if len(key) == 0 {
return
}
fmt.Printf("storage: put %v -> %v\n", key, value)
//fmt.Printf("storage: put %v -> %v\n", key, value)
s.data[key] = value
}
......@@ -47,7 +43,7 @@ func (s *EphemeralStorage) Get(key string) string {
if len(key) == 0 {
return ""
}
fmt.Printf("storage: get %v\n", key)
//fmt.Printf("storage: get %v\n", key)
if v, exist := s.data[key]; exist {
return v
}
......@@ -60,3 +56,11 @@ func NewEphemeralStorage() Storage {
}
return s
}
// NoStorage is a dummy construct which doesn't remember anything you tell it
type NoStorage struct{}
func (s *NoStorage) Put(key, value string) {}
func (s *NoStorage) Get(key string) string {
return ""
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment