Skip to content

Commit

Permalink
Adding PermissionsApi to agent and permissions() api to `Web5.dwn…
Browse files Browse the repository at this point in the history
…` with the ability to select delegate grants for `Web5.dwn` (#824)

* first pass at connect flow and grants api

* PermissionsApi for Agent, `permissions` API for `Web5` (#833)

This refactors a lot of what's in #824 with regards to creating/fetching grants.

Satisfies: #827

#### `PermissionsApi`
Introduces a `PermissionsApi` interface and an `AgentPermissionsApi` concrete implementation.

The interface implements the following methods `fetchGrants`, `fetchRequests`, `isGrantRevoked`, `createGrant`, `createRequest`, `createRevocation` as convenience methods for dealing with the built-in permission protocol records.

The `AgentPermissionsApi` implements an additional static method `matchGrantFromArray` which was moved from a `PermissionsUtil` class, which is used to find the appropriate grant to use when authoring a message.

#### `dwn.connected`
A Private API usedin a connected state to find and cache the correct grants to use for the request.

#### `dwn.permissions`
A Permissions API which implements `request`, `grant`, `queryRequests`, and `queryGrants` that a user can utilize

The `Web5` permissions api introduces 3 helper classes to represent permissions:
#### `PermissionRequest`
 Class to represent a permission request record. It implements convenience methods similar to the `Record` class where you can `store()`, `import()` or `send()` the underlying request record. Additionally a `grant()` method will create a `PermissionGrant` object.
 
#### `PermissionGrant`
 Class to represent a grant record. It implements convenience methods similar to the `Record` class where you can `store()`, `import()` or `send()` the underlying grant record. Additionally a `revoke()` method will create a `GrantRevocation` object, and `isRevoked()` will check if the underlying grant has been revoked.

#### `GrantRevocation`
 Class to represent a permission grant revocation record. It implements convenience methods similar to the `Record` class where you can `store()`  or `send()` the underlying revocation record.

* package lock update after rebase

* add additional comments around the signer for the permissions DWN api
  • Loading branch information
LiranCohen authored Aug 19, 2024
1 parent ac08f55 commit 0862ffc
Show file tree
Hide file tree
Showing 39 changed files with 13,211 additions and 6,024 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-baboons-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": minor
---

Support impersonation using delegated grants for DWN record operations using WalletConnect
5 changes: 5 additions & 0 deletions .changeset/gold-lamps-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": patch
---

Introduce a `grants` API for `Web5.dwn`
8 changes: 8 additions & 0 deletions .changeset/polite-days-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@web5/identity-agent": patch
"@web5/proxy-agent": patch
"@web5/user-agent": patch
"@web5/agent": patch
---

Introduce a `PermissionsApi` for Web5Agents
8 changes: 8 additions & 0 deletions .changeset/silly-poets-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@web5/agent": minor
"@web5/identity-agent": minor
"@web5/proxy-agent": minor
"@web5/user-agent": minor
---

Simplify support for Permission Grant logic within agent.
108 changes: 0 additions & 108 deletions packages/agent/src/dwn-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Readable } from '@web5/common';

import {
Cid,
DataEncodedRecordsWriteMessage,
DataStoreLevel,
Dwn,
DwnConfig,
Expand All @@ -11,10 +10,6 @@ import {
GenericMessage,
Message,
MessageStoreLevel,
PermissionGrant,
PermissionScope,
PermissionsProtocol,
RecordsWrite,
ResumableTaskStoreLevel
} from '@tbd54566975/dwn-sdk-js';

Expand All @@ -39,7 +34,6 @@ import type {

import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
import { blobToIsomorphicNodeReadable, getDwnServiceEndpointUrls, isRecordsWrite, webReadableToIsomorphicNodeReadable } from './utils.js';
import { DwnPermissionsUtil } from './dwn-permissions-util.js';

export type DwnMessageWithBlob<T extends DwnInterface> = {
message: DwnMessage[T];
Expand Down Expand Up @@ -450,106 +444,4 @@ export class AgentDwnApi {

return dwnMessageWithBlob;
}

/**
* NOTE EVERYTHING BELOW THIS LINE IS TEMPORARY
* TODO: Create a `grants` API to handle creating permission requests, grants and revocations
* */

/**
* Performs a RecordsQuery for permission grants that match the given parameters.
*/
public async fetchGrants({ author, target, grantee, grantor }: {
/** author of the query message, defaults to grantee */
author?: string,
/** target of the query message, defaults to author */
target?: string,
grantor: string,
grantee: string
}): Promise<DataEncodedRecordsWriteMessage[]> {
// if no author is provided, use the grantee's DID
author ??= grantee;
// if no target is explicitly provided, use the author
target ??= author;

const { reply: grantsReply } = await this.processRequest({
author,
target,
messageType : DwnInterface.RecordsQuery,
messageParams : {
filter: {
author : grantor, // the author of the grant would be the grantor and the logical author of the message
recipient : grantee, // the recipient of the grant would be the grantee
...DwnPermissionsUtil.permissionsProtocolParams('grant')
}
}
});

if (grantsReply.status.code !== 200) {
throw new Error(`AgentDwnApi: Failed to fetch grants: ${grantsReply.status.detail}`);
}

return grantsReply.entries! as DataEncodedRecordsWriteMessage[];
};

/**
* Check whether a grant is revoked by reading the revocation record for a given grant recordId.
*/
public async isGrantRevoked(author:string, target: string, grantRecordId: string): Promise<boolean> {
const { reply: revocationReply } = await this.processRequest({
author,
target,
messageType : DwnInterface.RecordsRead,
messageParams : {
filter: {
parentId: grantRecordId,
...DwnPermissionsUtil.permissionsProtocolParams('revoke')
}
}
});

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(`AgentDwnApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`);
}

public async createGrant({ grantedFrom, dateExpires, grantedTo, scope, delegated }:{
dateExpires: string,
grantedFrom: string,
grantedTo: string,
scope: PermissionScope,
delegated?: boolean
}): Promise<{
recordsWrite: RecordsWrite,
dataEncodedMessage: DataEncodedRecordsWriteMessage,
permissionGrantBytes: Uint8Array
}> {
return await PermissionsProtocol.createGrant({
signer: await this.getSigner(grantedFrom),
grantedTo,
dateExpires,
scope,
delegated
});
}

public async createRevocation({ grant, author }:{
author: string,
grant: PermissionGrant
}): Promise<{
recordsWrite: RecordsWrite,
dataEncodedMessage: DataEncodedRecordsWriteMessage,
permissionRevocationBytes: Uint8Array
}> {
return await PermissionsProtocol.createRevocation({
signer: await this.getSigner(author),
grant,
});
}
}
116 changes: 0 additions & 116 deletions packages/agent/src/dwn-permissions-util.ts

This file was deleted.

19 changes: 19 additions & 0 deletions packages/agent/src/identity-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,23 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
// Delete the Identity from the Agent's Identity store.
await this._store.delete({ id: didUri, agent: this.agent, tenant });
}

/**
* Returns the connected Identity, if one is available.
*
* Accepts optional `connectedDid` parameter to filter the a specific connected identity,
* if none is provided the first connected identity is returned.
*/
public async connectedIdentity({ connectedDid }:{ connectedDid?: string } = {}): Promise<BearerIdentity | undefined> {
const identities = await this.list();
if (identities.length < 1) {
return undefined;
}

// If a specific connected DID is provided, return the first identity that matches it.
// Otherwise, return the first connected identity.
return connectedDid ?
identities.find(identity => identity.metadata.connectedDid === connectedDid) :
identities.find(identity => identity.metadata.connectedDid !== undefined);
}
}
3 changes: 2 additions & 1 deletion packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ export * from './types/dwn.js';
export type * from './types/identity.js';
export type * from './types/identity-vault.js';
export type * from './types/key-manager.js';
export type * from './types/permissions.js';
export type * from './types/sync.js';
export type * from './types/vc.js';

export * from './bearer-identity.js';
export * from './crypto-api.js';
export * from './did-api.js';
export * from './dwn-api.js';
export * from './dwn-permissions-util.js';
export * from './dwn-registrar.js';
export * from './hd-identity-vault.js';
export * from './identity-api.js';
export * from './local-key-manager.js';
export * from './permissions-api.js';
export * from './rpc-client.js';
export * from './store-data.js';
export * from './store-did.js';
Expand Down
Loading

0 comments on commit 0862ffc

Please sign in to comment.