fea: nicer permissions
This commit is contained in:
@@ -1,9 +1,43 @@
|
|||||||
import json
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, root_validator
|
from pydantic import BaseModel, Field, root_validator
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ExtensionAPIMethodExport:
|
||||||
|
method_id: str
|
||||||
|
namespace: str
|
||||||
|
name: str
|
||||||
|
host_interface: str
|
||||||
|
host_name: str
|
||||||
|
sdk_name: str
|
||||||
|
description: str
|
||||||
|
required_permission: str | None = None
|
||||||
|
require_auth: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ExtensionAPIMethod:
|
||||||
|
method_id: str
|
||||||
|
namespace: str
|
||||||
|
name: str
|
||||||
|
python_name: str
|
||||||
|
host_interface: str
|
||||||
|
host_name: str
|
||||||
|
sdk_name: str
|
||||||
|
description: str
|
||||||
|
request_model: type[BaseModel]
|
||||||
|
response_model: type[BaseModel]
|
||||||
|
required_permission: str | None = None
|
||||||
|
require_auth: bool = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sdk_qualified_name(self) -> str:
|
||||||
|
return f"{self.namespace}.{self.sdk_name}"
|
||||||
|
|
||||||
|
|
||||||
class EmptyRequest(BaseModel):
|
class EmptyRequest(BaseModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -522,6 +522,17 @@ window.localisation.en = {
|
|||||||
extension_permissions_title: 'Grant extension permissions',
|
extension_permissions_title: 'Grant extension permissions',
|
||||||
extension_permissions_request: 'This extension requests these permissions:',
|
extension_permissions_request: 'This extension requests these permissions:',
|
||||||
extension_permissions_grant_install: 'Grant and install',
|
extension_permissions_grant_install: 'Grant and install',
|
||||||
|
extension_permissions_high_risk_warning:
|
||||||
|
'This extension requests permissions that can move funds.',
|
||||||
|
extension_permission_risk_low: 'Low risk',
|
||||||
|
extension_permission_risk_medium: 'Medium risk',
|
||||||
|
extension_permission_risk_high: 'High risk',
|
||||||
|
extension_permission_warning_wallet_pay_invoice:
|
||||||
|
'Can spend funds from wallets available to your account.',
|
||||||
|
extension_permission_warning_extension_api_request_write:
|
||||||
|
'Can write data or trigger actions in approved extensions.',
|
||||||
|
extension_permission_warning_http_request:
|
||||||
|
'Can send data to external services.',
|
||||||
extension_permission_ext_storage_read: 'Read extension storage',
|
extension_permission_ext_storage_read: 'Read extension storage',
|
||||||
extension_permission_ext_storage_read_public: 'Read public extension storage',
|
extension_permission_ext_storage_read_public: 'Read public extension storage',
|
||||||
extension_permission_ext_storage_write: 'Write extension storage',
|
extension_permission_ext_storage_write: 'Write extension storage',
|
||||||
|
|||||||
@@ -724,6 +724,11 @@ window.PageExtensions = {
|
|||||||
resolve(grantedPermissions)
|
resolve(grantedPermissions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
permissionGrantHasHighRisk() {
|
||||||
|
return this.permissionGrantDisplayItems().some(
|
||||||
|
permission => permission.risk.level === 'high'
|
||||||
|
)
|
||||||
|
},
|
||||||
permissionGrantDisplayItems() {
|
permissionGrantDisplayItems() {
|
||||||
const permissions = this.permissionGrant.permissions || []
|
const permissions = this.permissionGrant.permissions || []
|
||||||
const permissionsById = new Map(
|
const permissionsById = new Map(
|
||||||
@@ -781,6 +786,7 @@ window.PageExtensions = {
|
|||||||
label: isReadWriteStorage
|
label: isReadWriteStorage
|
||||||
? this.$t('extension_permission_ext_storage_read_write')
|
? this.$t('extension_permission_ext_storage_read_write')
|
||||||
: this.permissionLabel(permission),
|
: this.permissionLabel(permission),
|
||||||
|
risk: this.permissionRisk(permissions),
|
||||||
badges: [],
|
badges: [],
|
||||||
descriptions,
|
descriptions,
|
||||||
fieldGroups: [],
|
fieldGroups: [],
|
||||||
@@ -805,6 +811,68 @@ window.PageExtensions = {
|
|||||||
|
|
||||||
return item
|
return item
|
||||||
},
|
},
|
||||||
|
permissionRisk(permissions) {
|
||||||
|
const risks = permissions.map(permission =>
|
||||||
|
this.permissionRiskForPermission(permission)
|
||||||
|
)
|
||||||
|
const highestRisk = risks.find(risk => risk.level === 'high')
|
||||||
|
if (highestRisk) return highestRisk
|
||||||
|
return risks.find(risk => risk.level === 'medium') || this.lowRisk()
|
||||||
|
},
|
||||||
|
permissionRiskForPermission(permission) {
|
||||||
|
if (permission.id === 'wallet.pay_invoice') {
|
||||||
|
return this.highRisk('extension_permission_warning_wallet_pay_invoice')
|
||||||
|
}
|
||||||
|
if (permission.id === 'extension.api.request') {
|
||||||
|
const hasWriteAccess = this.extensionApiPermissionTargets(
|
||||||
|
permission
|
||||||
|
).some(target => target.access.includes('write'))
|
||||||
|
return hasWriteAccess
|
||||||
|
? this.highRisk(
|
||||||
|
'extension_permission_warning_extension_api_request_write'
|
||||||
|
)
|
||||||
|
: this.mediumRisk()
|
||||||
|
}
|
||||||
|
if (permission.id === 'http.request') {
|
||||||
|
return this.highRisk('extension_permission_warning_http_request')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'wallet.list',
|
||||||
|
'wallet.balance.read',
|
||||||
|
'wallet.create_invoice_public',
|
||||||
|
'ext.storage.read_public',
|
||||||
|
'payments.watch'
|
||||||
|
].includes(permission.id)
|
||||||
|
) {
|
||||||
|
return this.mediumRisk()
|
||||||
|
}
|
||||||
|
return this.lowRisk()
|
||||||
|
},
|
||||||
|
lowRisk() {
|
||||||
|
return {
|
||||||
|
level: 'low',
|
||||||
|
color: 'grey-6',
|
||||||
|
label: this.$t('extension_permission_risk_low'),
|
||||||
|
warning: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mediumRisk() {
|
||||||
|
return {
|
||||||
|
level: 'medium',
|
||||||
|
color: 'warning',
|
||||||
|
label: this.$t('extension_permission_risk_medium'),
|
||||||
|
warning: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highRisk(warningKey) {
|
||||||
|
return {
|
||||||
|
level: 'high',
|
||||||
|
color: 'negative',
|
||||||
|
label: this.$t('extension_permission_risk_high'),
|
||||||
|
warning: this.$t(warningKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
permissionOrderIndex(permissionId) {
|
permissionOrderIndex(permissionId) {
|
||||||
const order = [
|
const order = [
|
||||||
'wallet.pay_invoice',
|
'wallet.pay_invoice',
|
||||||
@@ -815,8 +883,8 @@ window.PageExtensions = {
|
|||||||
'ext.storage.read',
|
'ext.storage.read',
|
||||||
'ext.storage.write',
|
'ext.storage.write',
|
||||||
'ext.storage.read_public',
|
'ext.storage.read_public',
|
||||||
'wallet.create_invoice',
|
|
||||||
'wallet.create_invoice_public',
|
'wallet.create_invoice_public',
|
||||||
|
'wallet.create_invoice',
|
||||||
'utils.basic'
|
'utils.basic'
|
||||||
]
|
]
|
||||||
const index = order.indexOf(permissionId)
|
const index = order.indexOf(permissionId)
|
||||||
|
|||||||
@@ -467,6 +467,16 @@
|
|||||||
class="text-body2 q-mt-sm"
|
class="text-body2 q-mt-sm"
|
||||||
v-text="$t('extension_permissions_request')"
|
v-text="$t('extension_permissions_request')"
|
||||||
></div>
|
></div>
|
||||||
|
<q-banner
|
||||||
|
v-if="permissionGrantHasHighRisk()"
|
||||||
|
dense
|
||||||
|
class="bg-red-1 text-red-10 q-mt-md"
|
||||||
|
>
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon name="warning" color="negative"></q-icon>
|
||||||
|
</template>
|
||||||
|
<span v-text="$t('extension_permissions_high_risk_warning')"></span>
|
||||||
|
</q-banner>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-list bordered separator class="q-mt-md">
|
<q-list bordered separator class="q-mt-md">
|
||||||
@@ -475,27 +485,44 @@
|
|||||||
:key="permission.id"
|
:key="permission.id"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
|
class="q-pt-xs"
|
||||||
>
|
>
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label
|
<q-item-label class="text-weight-medium">
|
||||||
class="text-weight-medium"
|
<span v-text="permission.label"></span>
|
||||||
v-text="permission.label"
|
</q-item-label>
|
||||||
></q-item-label>
|
</q-item-section>
|
||||||
<q-item-label v-if="permission.badges.length" caption>
|
<q-item-section
|
||||||
|
v-if="permission.risk.level !== 'low' || permission.badges.length"
|
||||||
|
side
|
||||||
|
top
|
||||||
|
>
|
||||||
|
<div class="row items-center justify-end q-gutter-xs">
|
||||||
<q-badge
|
<q-badge
|
||||||
v-for="badge of permission.badges"
|
v-for="badge of permission.badges"
|
||||||
:key="badge.key"
|
:key="badge.key"
|
||||||
outline
|
outline
|
||||||
color="primary"
|
color="primary"
|
||||||
class="q-mr-xs q-mt-xs"
|
|
||||||
v-text="badge.label"
|
v-text="badge.label"
|
||||||
></q-badge>
|
></q-badge>
|
||||||
</q-item-label>
|
<q-badge
|
||||||
|
v-if="permission.risk.level !== 'low'"
|
||||||
|
:color="permission.risk.color"
|
||||||
|
v-text="permission.risk.label"
|
||||||
|
></q-badge>
|
||||||
|
</div>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="q-px-md q-pb-sm">
|
<div class="q-px-md q-pb-sm">
|
||||||
|
<div
|
||||||
|
v-if="permission.risk.warning"
|
||||||
|
class="row items-center text-negative text-caption q-mb-xs"
|
||||||
|
>
|
||||||
|
<q-icon name="warning" size="16px" class="q-mr-xs"></q-icon>
|
||||||
|
<span v-text="permission.risk.warning"></span>
|
||||||
|
</div>
|
||||||
<p
|
<p
|
||||||
v-for="description of permission.descriptions"
|
v-for="description of permission.descriptions"
|
||||||
:key="description"
|
:key="description"
|
||||||
|
|||||||
Reference in New Issue
Block a user