ask permissions

fix_save
filip mertens 3 years ago
parent 9da8612e79
commit 02f2cec119
  1. 11
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  2. 52
      apps/remix-ide/src/remixAppManager.js
  3. 4
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  4. 3
      libs/remix-ui/permission-handler/src/interface/index.ts
  5. 3
      libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx

@ -70,12 +70,12 @@ export class PermissionHandlerPlugin extends Plugin {
* @param {string} message from the caller plugin to add more details if needed * @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async askPermission(from: Profile, to: Profile, method: string, message: string) { async askPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
try { try {
this.permissions = this._getFromLocal() this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {} if (!this.permissions[to.name]) this.permissions[to.name] = {}
if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {} if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {}
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message) if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message, sensitiveCall)
const { allow, hash } = this.permissions[to.name][method][from.name] const { allow, hash } = this.permissions[to.name][method][from.name]
if (!allow) { if (!allow) {
@ -85,20 +85,21 @@ export class PermissionHandlerPlugin extends Plugin {
} }
return hash === from.hash return hash === from.hash
? true // Allow ? true // Allow
: await this.openPermission(from, to, method, message) : await this.openPermission(from, to, method, message, sensitiveCall)
} catch (err) { } catch (err) {
throw new Error(err) throw new Error(err)
} }
} }
async openPermission(from: Profile, to: Profile, method: string, message: string) { async openPermission(from: Profile, to: Profile, method: string, message: string, sensitiveCall: boolean) {
const remember = this.permissions[to.name][method][from.name] const remember = this.permissions[to.name][method][from.name]
const value: PermissionHandlerValue = { const value: PermissionHandlerValue = {
from, from,
to, to,
method, method,
message, message,
remember remember,
sensitiveCall
} }
const modal: AppModal = { const modal: AppModal = {
id: 'PermissionHandler', id: 'PermissionHandler',

@ -12,7 +12,12 @@ const requiredModules = [ // services + layout views + system views
const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) { const sensitiveCalls = {
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
'contentImport': ['resolveAndSave']
}
export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
@ -27,34 +32,34 @@ export function isNative (name) {
* @param {any, any} * @param {any, any}
* @returns {boolean} * @returns {boolean}
*/ */
export function canActivate (from, to) { export function canActivate(from, to) {
return ['ethdoc'].includes(from.name) || return ['ethdoc'].includes(from.name) ||
isNative(from.name) || isNative(from.name) ||
(to && from && from.canActivate && from.canActivate.includes(to.name)) (to && from && from.canActivate && from.canActivate.includes(to.name))
} }
export class RemixAppManager extends PluginManager { export class RemixAppManager extends PluginManager {
constructor () { constructor() {
super() super()
this.event = new EventEmitter() this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json' this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader() this.pluginLoader = new PluginLoader()
} }
async canActivatePlugin (from, to) { async canActivatePlugin(from, to) {
return canActivate(from, to) return canActivate(from, to)
} }
async canDeactivatePlugin (from, to) { async canDeactivatePlugin(from, to) {
if (requiredModules.includes(to.name)) return false if (requiredModules.includes(to.name)) return false
return isNative(from.name) return isNative(from.name)
} }
async canDeactivate(from,to) { async canDeactivate(from, to) {
return this.canDeactivatePlugin(from, to) return this.canDeactivatePlugin(from, to)
} }
async deactivatePlugin (name) { async deactivatePlugin(name) {
const [to, from] = [ const [to, from] = [
await this.getProfile(name), await this.getProfile(name),
await this.getProfile(this.requestFrom) await this.getProfile(this.requestFrom)
@ -64,52 +69,55 @@ export class RemixAppManager extends PluginManager {
} }
} }
async canCall (from, to, method, message) { async canCall(from, to, method, message) {
const isSensitiveCall = sensitiveCalls[to] && sensitiveCalls[to].includes(method)
// Make sure the caller of this methods is the target plugin // Make sure the caller of this methods is the target plugin
if (to !== this.currentRequest.from) { if (to !== this.currentRequest.from) {
return false return false
} }
// skipping native plugins' requests // skipping native plugins' requests
if (isNative(from)) { if (isNative(from) && !isSensitiveCall) {
return true return true
} }
// ask the user for permission // ask the user for permission
return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message) return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message, isSensitiveCall)
} }
onPluginActivated (plugin) { onPluginActivated(plugin) {
this.pluginLoader.set(plugin, this.actives) this.pluginLoader.set(plugin, this.actives)
this.event.emit('activate', plugin) this.event.emit('activate', plugin)
this.emit('activate', plugin) this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name]) if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
} }
getAll () { getAll() {
return Object.keys(this.profiles).map((p) => { return Object.keys(this.profiles).map((p) => {
return this.profiles[p] return this.profiles[p]
}) })
} }
getIds () { getIds() {
return Object.keys(this.profiles) return Object.keys(this.profiles)
} }
onPluginDeactivated (plugin) { onPluginDeactivated(plugin) {
this.pluginLoader.set(plugin, this.actives) this.pluginLoader.set(plugin, this.actives)
this.event.emit('deactivate', plugin) this.event.emit('deactivate', plugin)
_paq.push(['trackEvent', 'pluginManager', 'deactivate', plugin.name]) _paq.push(['trackEvent', 'pluginManager', 'deactivate', plugin.name])
} }
isDependent (name) { isDependent(name) {
return dependentModules.includes(name) return dependentModules.includes(name)
} }
isRequired (name) { isRequired(name) {
// excluding internal use plugins // excluding internal use plugins
return requiredModules.includes(name) return requiredModules.includes(name)
} }
async registeredPlugins () { async registeredPlugins() {
let plugins let plugins
try { try {
const res = await fetch(this.pluginsDirectory) const res = await fetch(this.pluginsDirectory)
@ -138,7 +146,7 @@ export class RemixAppManager extends PluginManager {
}) })
} }
async registerContextMenuItems () { async registerContextMenuItems() {
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'flattener', id: 'flattener',
name: 'flattenFileCustomAction', name: 'flattenFileCustomAction',
@ -167,11 +175,11 @@ export class RemixAppManager extends PluginManager {
* (localStorage, queryParams) * (localStorage, queryParams)
**/ **/
class PluginLoader { class PluginLoader {
get currentLoader () { get currentLoader() {
return this.loaders[this.current] return this.loaders[this.current]
} }
constructor () { constructor() {
const queryParams = new QueryParams() const queryParams = new QueryParams()
this.donotAutoReload = ['remixd', 'git'] // that would be a bad practice to force loading some plugins at page load. this.donotAutoReload = ['remixd', 'git'] // that would be a bad practice to force loading some plugins at page load.
this.loaders = {} this.loaders = {}
@ -195,11 +203,11 @@ class PluginLoader {
this.current = queryParams.get().activate ? 'queryParams' : 'localStorage' this.current = queryParams.get().activate ? 'queryParams' : 'localStorage'
} }
set (plugin, actives) { set(plugin, actives) {
this.currentLoader.set(plugin, actives) this.currentLoader.set(plugin, actives)
} }
get () { get() {
return this.currentLoader.get() return this.currentLoader.get()
} }
} }

@ -125,7 +125,7 @@ export class CompilerImports extends Plugin {
async resolveAndSave (url, targetPath) { async resolveAndSave (url, targetPath) {
try { try {
if (targetPath && this.currentRequest) { if (targetPath && this.currentRequest) {
const canCall = await this.askUserPermission('writeFile', 'This action will update the path ' + targetPath) const canCall = await this.askUserPermission('resolveAndSave', 'This action will update the path ' + targetPath)
if (!canCall) throw new Error('No permission to update ' + targetPath) if (!canCall) throw new Error('No permission to update ' + targetPath)
} }
const provider = await this.call('fileManager', 'getProviderOf', url) const provider = await this.call('fileManager', 'getProviderOf', url)
@ -181,7 +181,7 @@ export class CompilerImports extends Plugin {
} }
} }
} catch (e) { } catch (e) {
throw new Error(`not found ${url}`) throw new Error(e)
} }
} }
} }

@ -5,7 +5,8 @@ export interface PermissionHandlerValue {
to: Profile, to: Profile,
remember: boolean, remember: boolean,
method: string, method: string,
message: string message: string,
sensitiveCall: boolean
} }
export interface PermissionHandlerProps { export interface PermissionHandlerProps {

@ -3,7 +3,7 @@ import { PermissionHandlerProps } from '../interface'
import './permission-dialog.css' import './permission-dialog.css'
const PermissionHandlerDialog = (props: PermissionHandlerProps) => { const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
const { from, to, remember, method, message } = props.value const { from, to, remember, method, message, sensitiveCall } = props.value
const [feedback, setFeedback] = useState<string>('') const [feedback, setFeedback] = useState<string>('')
const theme = props.theme const theme = props.theme
@ -43,7 +43,6 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
</div> : null </div> : null
} }
const sensitiveCall = method === 'writeFile'
return (<section className="permission"> return (<section className="permission">
{pluginsImages()} {pluginsImages()}
<article> <article>

Loading…
Cancel
Save