Fix/Update method permissions (#7233)

* Use hex everywhere as appId

* Extract requestGroup sub item from clarity

* Change size of icon in SignerPending
This commit is contained in:
Amaury Martiny 2017-12-07 15:10:54 +01:00 committed by Jaco Greeff
parent 3cb4d81eb1
commit f6f7a87dc6
13 changed files with 175 additions and 106 deletions

30
js/package-lock.json generated
View File

@ -53,7 +53,7 @@
"dev": true,
"requires": {
"@parity/api": "2.1.5",
"@parity/shared": "2.2.8",
"@parity/shared": "2.2.9",
"@parity/ui": "2.2.15",
"lodash": "4.17.4",
"mobx": "3.3.2",
@ -76,7 +76,7 @@
"requires": {
"@parity/api": "2.1.5",
"@parity/etherscan": "2.1.3",
"@parity/shared": "2.2.8",
"@parity/shared": "2.2.9",
"bignumber.js": "3.0.1",
"brace": "0.9.0",
"date-difference": "1.0.0",
@ -578,7 +578,7 @@
"requires": {
"@parity/api": "2.1.5",
"@parity/etherscan": "2.1.3",
"@parity/shared": "2.2.8",
"@parity/shared": "2.2.9",
"bignumber.js": "3.0.1",
"brace": "0.9.0",
"date-difference": "1.0.0",
@ -1151,9 +1151,9 @@
"version": "github:paritytech/plugin-signer-qr#c16423de5b8a8f68ebd5f1e78e084fa959329a9f"
},
"@parity/shared": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/@parity/shared/-/shared-2.2.8.tgz",
"integrity": "sha512-gSF4rX2dsjEHAlWvCSNRPeWRSGBKLxev6ke6othzmO4AluTK71A4ZM3N18fw5NTybCiiELbdNUPkxzCtvs92Ew==",
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@parity/shared/-/shared-2.2.9.tgz",
"integrity": "sha512-bbzfcFNt0XsO9oJj3ySPfMgFAcPtFJElHbEXTm4xU4xntADoSIYuWtvonq2uULxA1LQ1O+1hGpyagQ8OZ+YoqQ==",
"requires": {
"@parity/ledger": "2.1.2",
"eventemitter3": "2.0.3",
@ -1215,7 +1215,7 @@
"requires": {
"@parity/api": "2.1.5",
"@parity/etherscan": "2.1.3",
"@parity/shared": "2.2.8",
"@parity/shared": "2.2.9",
"babel-runtime": "6.26.0",
"bignumber.js": "4.1.0",
"brace": "0.11.0",
@ -2371,7 +2371,6 @@
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
"integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
"dev": true,
"requires": {
"babel-plugin-transform-strict-mode": "6.24.1",
"babel-runtime": "6.26.0",
@ -2709,7 +2708,7 @@
"babel-plugin-transform-es2015-function-name": "6.24.1",
"babel-plugin-transform-es2015-literals": "6.22.0",
"babel-plugin-transform-es2015-modules-amd": "6.24.1",
"babel-plugin-transform-es2015-modules-commonjs": "6.26.0",
"babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
"babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
"babel-plugin-transform-es2015-modules-umd": "6.24.1",
"babel-plugin-transform-es2015-object-super": "6.24.1",
@ -2721,19 +2720,6 @@
"babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
"babel-plugin-transform-es2015-unicode-regex": "6.24.1",
"babel-plugin-transform-regenerator": "6.26.0"
},
"dependencies": {
"babel-plugin-transform-es2015-modules-commonjs": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz",
"integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=",
"requires": {
"babel-plugin-transform-strict-mode": "6.24.1",
"babel-runtime": "6.26.0",
"babel-template": "6.26.0",
"babel-types": "6.26.0"
}
}
}
},
"babel-preset-flow": {

View File

@ -147,7 +147,7 @@
"@parity/plugin-signer-default": "paritytech/plugin-signer-default",
"@parity/plugin-signer-hardware": "paritytech/plugin-signer-hardware",
"@parity/plugin-signer-qr": "paritytech/plugin-signer-qr",
"@parity/shared": "^2.2.8",
"@parity/shared": "^2.2.9",
"@parity/ui": "^3.0.14",
"keythereum": "1.0.2",
"lodash.flatten": "4.4.0",

View File

@ -6,4 +6,16 @@ To be clear with the terminology used in the code here:
- a *methodGroup* is the grouping of similar methods (see `methodGroups.js`)
- a *permission* is a boolean which tells if an app is allowed to call a method or not
- a *request* is when an app prompts the shell to call a method
- a *requestGroup* is an array of *requests* whose methods are in the same *methodGroup*
- a *requestGroup* is a map of the following form
```javascript
{
appId1: {
methodGroup1: [request1, request2] // This is a requestGroup sub-item
},
appId2: {
methodGroup1: [request1]
methodGroup2: [request3]
},
// ...
}
```

View File

@ -0,0 +1,24 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.requestGroupSubItem {
margin-top: 2px;
.requestGroupSubItemTitle {
margin-right: 10px;
}
}

View File

@ -0,0 +1,76 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Popup from 'semantic-ui-react/dist/commonjs/modules/Popup';
import Button from '@parity/ui/lib/Button';
import methodGroups from '../../methodGroups';
import styles from './RequestGroupSubItem.css';
export default class RequestGroupSubItem extends PureComponent {
handleApprove = () => this.props.onApprove(this.props.requests, this.props.groupId)
handleReject = () => this.props.onReject(this.props.requests)
render () {
const { groupId } = this.props;
return (
<div className={ styles.requestGroupSubItem }>
<span className={ styles.requestGroupSubItemTitle }>
Permission for{' '}
<Popup
trigger={ <span>{groupId}</span> }
content={ `Requested methods: ${methodGroups[groupId].methods.join(', ')}` }
/>
</span>
<Button
size='mini'
label={
<FormattedMessage
id='dappRequests.request.buttons.approve'
defaultMessage='Approve'
/>
}
onClick={ this.handleApprove }
/>
<Button
size='mini'
label={
<FormattedMessage
id='dappRequests.request.buttons.reject'
defaultMessage='Reject'
/>
}
onClick={ this.handleReject }
/>
</div>
);
}
}
RequestGroupSubItem.propTypes = {
className: PropTypes.string,
groupId: PropTypes.string,
onApprove: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
requests: PropTypes.array.isRequired
};

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './RequestGroupSubItem';

View File

@ -28,12 +28,4 @@ $backgroundTwo: #e57a00;
> span {
margin-right: 30px;
}
.requestGroup {
margin-top: 2px;
.requestGroupTitle {
margin-right: 10px;
}
}
}

View File

@ -14,49 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PureComponent } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Popup from 'semantic-ui-react/dist/commonjs/modules/Popup';
import Button from '@parity/ui/lib/Button';
import DappsStore from '@parity/shared/lib/mobx/dappsStore';
import RequestGroupSubItem from './RequestGroupSubItem';
import styles from './RequestGroups.css';
export default class RequestGroups extends PureComponent {
state = {
opened: false
};
handleApproveRequestGroup = groupId => {
const { requestGroups, onApproveRequestGroup } = this.props;
onApproveRequestGroup(Object.values(requestGroups[groupId].map(({ requestId }) => requestId)));
}
handleRejectRequestGroup = groupId => {
const { requestGroups, onRejectRequestGroup } = this.props;
onRejectRequestGroup(Object.values(requestGroups[groupId].map(({ requestId }) => requestId)));
}
renderPopupContent = groupId => {
const { requestGroups } = this.props;
// Get unique list of methods in that request group
const requestedMethods = [...new Set(
Object.values(requestGroups[groupId])
.map(request => request.data.method || request.data.params[0])
)];
return `Requested methods: ${requestedMethods.join(', ')}`;
export default class RequestGroups extends Component {
handleApproveRequestGroup = (requests, groupId) => {
this.props.onApproveRequestGroup(requests, groupId, this.props.appId);
}
render () {
const {
appId,
requestGroups
requestGroups,
onRejectRequestGroup
} = this.props;
const app = DappsStore.get().getAppById(appId);
@ -72,35 +48,13 @@ export default class RequestGroups extends PureComponent {
} }
/>
{Object.keys(requestGroups).map(groupId => (
<div key={ `${appId}-${groupId}` } className={ styles.requestGroup }>
<span className={ styles.requestGroupTitle }>
Permission for{' '}
<Popup
trigger={ <span>{groupId}</span> }
content={ this.renderPopupContent(groupId) }
<RequestGroupSubItem
key={ `${appId}-${groupId}` }
groupId={ groupId }
requests={ requestGroups[groupId] }
onApprove={ this.handleApproveRequestGroup }
onReject={ onRejectRequestGroup }
/>
</span>
<Button
size='mini'
label={
<FormattedMessage
id='dappRequests.request.buttons.approve'
defaultMessage='Approve'
/>
}
onClick={ () => this.handleApproveRequestGroup(groupId) }
/>
<Button
size='mini'
label={
<FormattedMessage
id='dappRequests.request.buttons.reject'
defaultMessage='Reject'
/>
}
onClick={ () => this.handleRejectRequestGroup(groupId) }
/>
</div>
))}
</div>
);

View File

@ -17,6 +17,7 @@
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import methodGroups from './methodGroups';
import RequestGroups from './RequestGroups';
import Store from './store';
import styles from './dappRequests.css';
@ -24,12 +25,16 @@ import styles from './dappRequests.css';
class DappRequests extends Component {
store = Store.get();
handleApproveRequestGroup = requestIds => {
requestIds.forEach(this.store.approveRequest);
// When we approve a requestGroup, when approve all the requests, and add permissions
// to all the other methods in the same methodGroup
handleApproveRequestGroup = (requests, groupId, appId) => {
requests.map(({ requestId }) => requestId).forEach(this.store.approveRequest);
methodGroups[groupId].methods.forEach(method => this.store.addAppPermission(method, appId));
}
handleRejectRequestGroup = requestIds => {
requestIds.forEach(this.store.rejectRequest);
// When we reject a requestGroup, we reject the requests in that group
handleRejectRequestGroup = requests => {
requests.map(({ requestId }) => requestId).forEach(this.store.rejectRequest);
}
render () {

View File

@ -18,6 +18,7 @@ import { action, computed, observable } from 'mobx';
import store from 'store';
import { sha3 } from '@parity/api/lib/util/sha3';
import { isHex } from '@parity/api/lib/util/types';
import { methodGroupFromMethod } from './methodGroups';
@ -52,7 +53,7 @@ export default class Store {
accumulator[appId] = accumulator[appId] || {};
accumulator[appId][methodGroup] = accumulator[appId][methodGroup] || [];
accumulator[appId][methodGroup].push({ data, requestId }); // Append the requestId field in the request object
accumulator[appId][methodGroup].push({ data, requestId }); // Push request & append the requestId field in the request object
return accumulator;
}, {});
@ -99,7 +100,7 @@ export default class Store {
this.requests = { ...this.requests };
};
getPermissionId = (method, appId) => `${method}:${appId}` // Create an id to identify permissions based on method and appId
getPermissionId = (method, appId) => `${method}:${isHex(appId) ? appId : sha3(appId)}`; // Create an id to identify permissions based on method and appId
getMethodFromRequest = requestId => {
const { data: { method, params } } = this.requests[requestId];
@ -131,13 +132,10 @@ export default class Store {
};
setPermissions = _permissions => {
const permissions = {};
Object.keys(_permissions).forEach(id => {
permissions[id] = !!_permissions[id];
});
this.permissions = permissions;
this.permissions = {
...this.permissions,
..._permissions
};
this.savePermissions();
return true;

View File

@ -19,7 +19,6 @@
background: rgba(40, 40, 40, 0.85);
color: white;
line-height: 1.5em;
text-align: left;
padding: 4em 2em;
position: fixed;
top: 0;

View File

@ -19,6 +19,11 @@
display: flex !important;
}
.fromAvatar {
width: 28px !important;
height: 28px !important;
}
.toAvatar {
margin-left: 3px;
}

View File

@ -168,6 +168,7 @@ class RequestItem extends Component {
<List.Item onClick={ onClick }>
<Image avatar size='mini' verticalAlign='middle'>
<IdentityIcon
className={ styles.fromAvatar }
address={ transaction.from }
/>
</Image>