UNPKG

@web5/agent

Version:
322 lines 15.7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { PermissionsProtocol } from '@tbd54566975/dwn-sdk-js'; import { DwnInterface, DwnPermissionGrant, DwnPermissionRequest } from './types/dwn.js'; import { Convert, TtlCache } from '@web5/common'; import { isRecordsType } from './dwn-api.js'; export class AgentPermissionsApi { get agent() { if (!this._agent) { throw new Error('AgentPermissionsApi: Agent is not set'); } return this._agent; } set agent(agent) { this._agent = agent; } constructor({ agent } = {}) { /** cache for fetching a permission {@link PermissionGrant}, keyed by a specific MessageType and protocol */ this._cachedPermissions = new TtlCache({ ttl: 60 * 1000 }); this._agent = agent; } getPermissionForRequest({ connectedDid, delegateDid, delegate, messageType, protocol, cached = false }) { return __awaiter(this, void 0, void 0, function* () { // Currently we only support finding grants based on protocols // A different approach may be necessary when we introduce `protocolPath` and `contextId` specific impersonation const cacheKey = [connectedDid, delegateDid, messageType, protocol].join('~'); const cachedGrant = cached ? this._cachedPermissions.get(cacheKey) : undefined; if (cachedGrant) { return cachedGrant; } const permissionGrants = yield this.fetchGrants({ author: delegateDid, target: delegateDid, grantor: connectedDid, grantee: delegateDid, }); // get the delegate grants that match the messageParams and are associated with the connectedDid as the grantor const grant = yield AgentPermissionsApi.matchGrantFromArray(connectedDid, delegateDid, { messageType, protocol }, permissionGrants, delegate); if (!grant) { throw new Error(`CachedPermissions: No permissions found for ${messageType}: ${protocol}`); } this._cachedPermissions.set(cacheKey, grant); return grant; }); } fetchGrants({ author, target, grantee, grantor, protocol, remote = false }) { return __awaiter(this, void 0, void 0, function* () { // filter by a protocol using tags if provided const tags = protocol ? { protocol } : undefined; const params = { author: author, target: target, messageType: DwnInterface.RecordsQuery, messageParams: { filter: { author: grantor, recipient: grantee, protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.grantPath, tags } } }; const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params); if (reply.status.code !== 200) { throw new Error(`PermissionsApi: Failed to fetch grants: ${reply.status.detail}`); } const grants = []; for (const entry of reply.entries) { // TODO: Check for revocation status based on a request parameter and filter out revoked grants const grant = yield DwnPermissionGrant.parse(entry); grants.push({ grant, message: entry }); } return grants; }); } fetchRequests({ author, target, protocol, remote = false }) { return __awaiter(this, void 0, void 0, function* () { // filter by a protocol using tags if provided const tags = protocol ? { protocol } : undefined; const params = { author: author, target: target, messageType: DwnInterface.RecordsQuery, messageParams: { filter: { protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.requestPath, tags } } }; const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params); if (reply.status.code !== 200) { throw new Error(`PermissionsApi: Failed to fetch requests: ${reply.status.detail}`); } const requests = []; for (const entry of reply.entries) { const request = yield DwnPermissionRequest.parse(entry); requests.push({ request, message: entry }); } return requests; }); } isGrantRevoked({ author, target, grantRecordId, remote = false }) { return __awaiter(this, void 0, void 0, function* () { const params = { author, target, messageType: DwnInterface.RecordsRead, messageParams: { filter: { parentId: grantRecordId, protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.revocationPath, } } }; const { reply: revocationReply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params); if (revocationReply.status.code === 404) { // no revocation found, the grant is not revoked return false; } else if (revocationReply.status.code === 200) { // a revocation was found, the grant is revoked return true; } throw new Error(`PermissionsApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`); }); } createGrant(params) { return __awaiter(this, void 0, void 0, function* () { const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]); let tags = undefined; if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) { tags = { protocol: createGrantParams.scope.protocol }; } const permissionGrantData = { dateExpires: createGrantParams.dateExpires, requestId: createGrantParams.requestId, description: createGrantParams.description, delegated, scope: createGrantParams.scope }; const permissionsGrantBytes = Convert.object(permissionGrantData).toUint8Array(); const messageParams = { recipient: createGrantParams.grantedTo, protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.grantPath, dataFormat: 'application/json', tags }; const { reply, message } = yield this.agent.processDwnRequest({ store, author, target: author, messageType: DwnInterface.RecordsWrite, messageParams, dataStream: new Blob([permissionsGrantBytes]) }); if (reply.status.code !== 202) { throw new Error(`PermissionsApi: Failed to create grant: ${reply.status.detail}`); } const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionsGrantBytes).toBase64Url() }); const grant = yield DwnPermissionGrant.parse(dataEncodedMessage); return { grant, message: dataEncodedMessage }; }); } createRequest(params) { return __awaiter(this, void 0, void 0, function* () { const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]); let tags = undefined; if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) { tags = { protocol: createGrantParams.scope.protocol }; } const permissionRequestData = { description: createGrantParams.description, delegated, scope: createGrantParams.scope }; const permissionRequestBytes = Convert.object(permissionRequestData).toUint8Array(); const messageParams = { protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.requestPath, dataFormat: 'application/json', tags }; const { reply, message } = yield this.agent.processDwnRequest({ store, author, target: author, messageType: DwnInterface.RecordsWrite, messageParams, dataStream: new Blob([permissionRequestBytes]) }); if (reply.status.code !== 202) { throw new Error(`PermissionsApi: Failed to create request: ${reply.status.detail}`); } const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRequestBytes).toBase64Url() }); const request = yield DwnPermissionRequest.parse(dataEncodedMessage); return { request, message: dataEncodedMessage }; }); } createRevocation(params) { return __awaiter(this, void 0, void 0, function* () { const { author, store = false, grant, description } = params; const revokeData = { description }; const permissionRevocationBytes = Convert.object(revokeData).toUint8Array(); let tags = undefined; if (PermissionsProtocol.hasProtocolScope(grant.scope)) { tags = { protocol: grant.scope.protocol }; } const messageParams = { parentContextId: grant.id, protocol: PermissionsProtocol.uri, protocolPath: PermissionsProtocol.revocationPath, dataFormat: 'application/json', tags }; const { reply, message } = yield this.agent.processDwnRequest({ store, author, target: author, messageType: DwnInterface.RecordsWrite, messageParams, dataStream: new Blob([permissionRevocationBytes]) }); if (reply.status.code !== 202) { throw new Error(`PermissionsApi: Failed to create revocation: ${reply.status.detail}`); } const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRevocationBytes).toBase64Url() }); return { message: dataEncodedMessage }; }); } clear() { return __awaiter(this, void 0, void 0, function* () { this._cachedPermissions.clear(); }); } /** * Matches the appropriate grant from an array of grants based on the provided parameters. * * @param delegated if true, only delegated grants are turned, if false all grants are returned including delegated ones. */ static matchGrantFromArray(grantor, grantee, messageParams, grants, delegated = false) { return __awaiter(this, void 0, void 0, function* () { for (const entry of grants) { const { grant, message } = entry; if (delegated === true && grant.delegated !== true) { continue; } const { messageType, protocol, protocolPath, contextId } = messageParams; if (this.matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId)) { return { grant, message }; } } }); } static matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId) { // Check if the grant matches the provided parameters if (grant.grantee !== grantee || grant.grantor !== grantor) { return false; } const scope = grant.scope; const scopeMessageType = scope.interface + scope.method; if (scopeMessageType === messageType) { if (isRecordsType(messageType)) { const recordScope = scope; if (recordScope.protocol !== protocol) { return false; } // If the grant scope is not restricted to a specific context or protocol path, it is unrestricted and can be used if (this.isUnrestrictedProtocolScope(recordScope)) { return true; } // protocolPath and contextId are mutually exclusive // If the permission is scoped to a protocolPath and the permissionParams matches that path, this grant can be used if (recordScope.protocolPath !== undefined && recordScope.protocolPath === protocolPath) { return true; } // If the permission is scoped to a contextId and the permissionParams starts with that contextId, this grant can be used if (recordScope.contextId !== undefined && (contextId === null || contextId === void 0 ? void 0 : contextId.startsWith(recordScope.contextId))) { return true; } } else { const messagesScope = scope; // Checks for unrestricted protocol scope, if no protocol is defined in the scope it is unrestricted if (messagesScope.protocol === undefined) { return true; } if (messagesScope.protocol !== protocol) { return false; } return this.isUnrestrictedProtocolScope(messagesScope); } } return false; } static isUnrestrictedProtocolScope(scope) { return scope.contextId === undefined && scope.protocolPath === undefined; } } //# sourceMappingURL=permissions-api.js.map