feat: dapp browser privileges

- Create privilege dialogs dynamically for each privilege requested
- Check if a privilege has been granted before to determine if dialog must be shown or not
- If dapp is allowed to use privilege, save it in the settings
This commit is contained in:
Richard Ramos 2020-09-29 13:02:20 -04:00 committed by Iuri Matias
parent b6066ef1dd
commit f85216e7f7
4 changed files with 100 additions and 35 deletions

View File

@ -25,11 +25,6 @@ type
APIResponse = "api-response",
Web3ResponseError = "web3-response-error"
Permissions {.pure.} = enum
Web3 = "web3",
ContactCode = "contact-code"
Unknown = "unknown"
type
Payload = ref object
id: JsonNode
@ -43,7 +38,7 @@ type
APIRequest = ref object
isAllowed: bool
messageId: JsonNode
permission: Permissions
permission: Permission
hostname: string
proc requestType(message: string): RequestTypes =
@ -67,17 +62,11 @@ proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly =
proc toAPIRequest(message: string): APIRequest =
let data = message.parseJson
var permission = Permissions.Unknown
try:
permission = parseEnum[Permissions](data["permission"].getStr())
except:
warn "Unknown permission requested", value=data["permission"].getStr()
result = APIRequest(
messageId: data["messageId"],
isAllowed: data{"isAllowed"}.getBool(),
permission: permission,
permission: data["permission"].getStr().toPermission(),
hostname: data{"hostname"}.getStr()
)
@ -137,17 +126,17 @@ QtObject:
"result": rpcResult.parseJson
}
proc process*(data: APIRequest): string =
proc process*(data: APIRequest, status: Status): string =
var value:JsonNode = case data.permission
of Permissions.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")]
of Permissions.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0")
of Permissions.Unknown: newJNull()
of Permission.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")]
of Permission.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0")
of Permission.Unknown: newJNull()
let isAllowed = data.isAllowed and data.permission != Permissions.Unknown
let isAllowed = data.isAllowed and data.permission != Permission.Unknown
info "API request received", host=data.hostname, value=data.permission, isAllowed
# TODO: if isAllowed, store permission grant
if isAllowed: status.permissions.addPermission(data.hostname, data.permission)
return $ %* {
"type": ResponseTypes.APIResponse,
@ -157,11 +146,14 @@ QtObject:
"data": value
}
proc hasPermission*(self: Web3ProviderView, hostname: string, permission: string): bool {.slot.} =
result = self.status.permissions.hasPermission(hostname, permission.toPermission())
proc postMessage*(self: Web3ProviderView, message: string): string {.slot.} =
case message.requestType():
of RequestTypes.Web3SendAsyncReadOnly: message.toWeb3SendAsyncReadOnly().process()
of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO:
of RequestTypes.APIRequest: message.toAPIRequest().process()
of RequestTypes.APIRequest: message.toAPIRequest().process(self.status)
else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO:
proc getNetworkId*(self: Web3ProviderView): int {.slot.} = getCurrentNetworkDetails().config.networkId

View File

@ -0,0 +1,62 @@
import json
import strutils
import sets
import libstatus/core
import libstatus/types
import chronicles
import eventemitter
import sequtils
type
Permission* {.pure.} = enum
Web3 = "web3",
ContactCode = "contact-code"
Unknown = "unknown"
logScope:
topics = "permissions-model"
type
PermissionsModel* = ref object
events*: EventEmitter
proc toPermission*(value: string): Permission =
result = Permission.Unknown
try:
result = parseEnum[Permission](value)
except:
warn "Unknown permission requested", value
proc newPermissionsModel*(events: EventEmitter): PermissionsModel =
result = PermissionsModel()
result.events = events
proc init*(self: PermissionsModel) =
discard
proc getPermissions*(self: PermissionsModel, dapp: string): HashSet[Permission] =
let response = callPrivateRPC("permissions_getDappPermissions")
result = initHashSet[Permission]()
for dappPermission in response.parseJson["result"].getElems():
if dappPermission["dapp"].getStr() == dapp:
for permission in dappPermission["permissions"].getElems():
result.incl(permission.getStr().toPermission())
proc hasPermission*(self: PermissionsModel, dapp: string, permission: Permission): bool =
result = self.getPermissions(dapp).contains(permission)
proc addPermission*(self: PermissionsModel, dapp: string, permission: Permission) =
var permissions = self.getPermissions(dapp)
permissions.incl(permission)
discard callPrivateRPC("permissions_addDappPermissions", %*[{
"dapp": dapp,
"permissions": permissions.toSeq()
}])
proc revokePermission*(self: PermissionsModel, dapp: string, permission: Permission) =
# TODO: implement
discard
proc clearPermissions*(self: PermissionsModel, dapp: string) =
# TODO: implement
discard

View File

@ -2,10 +2,10 @@ import libstatus/accounts as libstatus_accounts
import libstatus/core as libstatus_core
import libstatus/settings as libstatus_settings
import libstatus/types as libstatus_types
import chat, accounts, wallet, node, network, mailservers, messages, contacts, profile, stickers
import chat, accounts, wallet, node, network, mailservers, messages, contacts, profile, stickers, permissions
import ../eventemitter
export chat, accounts, node, mailservers, messages, contacts, profile, network
export chat, accounts, node, mailservers, messages, contacts, profile, network, permissions
type Status* = ref object
events*: EventEmitter
@ -19,6 +19,7 @@ type Status* = ref object
contacts*: ContactModel
network*: NetworkModel
stickers*: StickersModel
permissions*: PermissionsModel
proc newStatusInstance*(): Status =
result = Status()
@ -34,6 +35,7 @@ proc newStatusInstance*(): Status =
result.contacts = contacts.newContactModel(result.events)
result.network = network.newNetworkModel(result.events)
result.stickers = stickers.newStickersModel(result.events)
result.permissions = permissions.newPermissionsModel(result.events)
proc initNode*(self: Status) =
libstatus_accounts.initNode()

View File

@ -25,17 +25,21 @@ Item {
Layout.fillHeight: true
Layout.fillWidth: true
property var request: {"hostname": "", "permission": ""}
function postMessage(isAllowed){
request.isAllowed = isAllowed;
provider.web3Response(web3Provider.postMessage(JSON.stringify(request)));
}
ModalPopup {
property Component accessDialogComponent: ModalPopup {
id: accessDialog
property var request: {"hostname": "", "permission": ""}
function postMessage(isAllowed){
request.isAllowed = isAllowed;
provider.web3Response(web3Provider.postMessage(JSON.stringify(request)));
}
onClosed: {
accessDialog.destroy();
}
// TODO: design required
StyledText {
@ -66,7 +70,7 @@ Item {
switch(request.permission){
case Constants.permission_web3: return qsTr("Allowing authorizes this DApp to retrieve your wallet address and enable Web3");
case Constants.permission_contactCode: return qsTr("Granting access authorizes this DApp to retrieve your chat key");
default: return qsTr("Unknown permission");
default: return qsTr("Unknown permission: " + request.permission);
}
}
}
@ -104,11 +108,16 @@ Item {
signal web3Response(string data);
function postMessage(data){
request = JSON.parse(data)
var request = JSON.parse(data)
if(request.type === Constants.api_request){
// TODO: check if permission has been granted before,
// to not show the dialog
accessDialog.open()
if(!web3Provider.hasPermission(request.hostname, request.permission)){
var dialog = accessDialogComponent.createObject(browserWindow);
dialog.request = request;
dialog.open();
} else {
request.isAllowed = true;
web3Response(web3Provider.postMessage(JSON.stringify(request)));
}
} else {
web3Response(web3Provider.postMessage(data));
}