Skip to content

Commit

Permalink
feat: 🎸 enable unscoped custom claims and fix get scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
sansan committed Nov 6, 2024
1 parent b55e637 commit 3eeb180
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 23 deletions.
111 changes: 95 additions & 16 deletions src/api/client/Claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ import {
ClaimOperation,
ClaimScope,
ClaimType,
CustomClaim,
CustomClaimType,
CustomClaimTypeWithDid,
CustomClaimWithoutScope,
ErrorCode,
IdentityWithClaims,
ModifyClaimsParams,
NextKey,
ProcedureMethod,
RegisterCustomClaimTypeParams,
ResultSet,
Expand Down Expand Up @@ -273,12 +276,77 @@ export class Claims {
* If the scope is an asset DID, the corresponding ticker is returned as well
*
* @param opts.target - Identity for which to fetch claim scopes (optional, defaults to the signing Identity)
* @note in order for scopes to include scopes for custom claims, middlewareV2 is required
*/
public async getClaimScopes(opts: { target?: string | Identity } = {}): Promise<ClaimScope[]> {
const { context } = this;
const { target } = opts;

const did = await getDid(target, context);
const [did, isMiddlewareAvailable] = await Promise.all([
getDid(target, context),
context.isMiddlewareAvailable(),
]);

const customClaimScopeList: ClaimScope[] = [];

if (isMiddlewareAvailable) {
const { data, count, next } = await this.getIdentitiesWithClaims({
targets: [did],
claimTypes: [ClaimType.Custom],
includeExpired: false,
});
const getClaimScopeFromClaim = (
claim: ClaimData<CustomClaim | CustomClaimWithoutScope>
): ClaimScope => {
if (claim.claim.scope) {
return {
scope: claim.claim.scope,
ticker:
claim.claim.scope.type === ScopeType.Ticker ? claim.claim.scope.value : undefined,
};
}

return { scope: null };
};

data.forEach(record => {
const { claims: claimData } = record as {
claims: ClaimData<CustomClaim | CustomClaimWithoutScope>[];
};

customClaimScopeList.push(...claimData.map(getClaimScopeFromClaim));
});

if (next && count) {
// fetch remaining scopes
const promises = [];

let start: NextKey = next;

while (start && BigNumber.isBigNumber(start)) {
promises.push(
this.getIdentitiesWithClaims({
targets: [did],
claimTypes: [ClaimType.Custom],
includeExpired: false,
start,
})
);

start = calculateNextKey(count, data.length, start);
}

const results = await Promise.all(promises);

results.forEach(record => {
record.data.forEach(result => {
const { claims: claimData } = result as { claims: ClaimData<CustomClaim>[] };

customClaimScopeList.push(...claimData.map(getClaimScopeFromClaim));
});
});
}
}

const identityClaimsFromChain = await context.getIdentityClaimsFromChain({
targets: [did],
Expand All @@ -296,27 +364,38 @@ export class Claims {
includeExpired: true,
});

const claimScopeList = identityClaimsFromChain.map(({ claim }) => {
// only Scoped Claims were fetched so this assertion is reasonable
const {
scope: { type, value },
} = claim as ScopedClaim;
const claimScopeList = identityClaimsFromChain
// @ts-expect-error - only Scoped Claims were fetched so this assertion is reasonable
.filter(claim => claim.claim.scope)
.map(({ claim }) => {
// only Scoped Claims were fetched so this assertion is reasonable
const {
scope: { type, value },
} = claim as ScopedClaim;

let ticker: string | undefined;
let ticker: string | undefined;

/* istanbul ignore if: this will be removed after dual version support for v6-v7 */
// prettier-ignore
if (type === ScopeType.Ticker) { // NOSONAR
/* istanbul ignore if: this will be removed after dual version support for v6-v7 */
// prettier-ignore
if (type === ScopeType.Ticker) { // NOSONAR
ticker = removePadding(value);
}

return {
scope: { type, value: ticker ?? value },
ticker,
};
});
return {
scope: {
type,
value:
/* istanbul ignore next: this will be removed after dual version support for v6-v7 */ ticker ??
value,
},
ticker,
};
});

return uniqWith(claimScopeList, isEqual);
return uniqWith(
[...claimScopeList, ...customClaimScopeList.filter(scope => scope.scope)],
isEqual
);
}

/**
Expand Down
193 changes: 193 additions & 0 deletions src/api/client/__tests__/Claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ describe('Claims Class', () => {
contextOptions: {
did: target,
getIdentityClaimsFromChain: fakeClaimData,
middlewareAvailable: false,
},
});

Expand All @@ -550,6 +551,198 @@ describe('Claims Class', () => {

expect(result.length).toEqual(2);
});

it('should return a list of scopes and tickers with middleware enabled', async () => {
const target = 'someTarget';
const someDid = 'someDid';
const ticker = 'someTicker';

const fakeClaimData = [
{
claim: {
type: ClaimType.Jurisdiction,
scope: {
type: ScopeType.Identity,
value: someDid,
},
},
},
{
claim: {
type: ClaimType.Jurisdiction,
scope: {
type: ScopeType.Asset,
value: '0x12341234123412341234123412341234',
},
},
},
] as ClaimData[];

dsMockUtils.configureMocks({
contextOptions: {
did: target,
getIdentityClaimsFromChain: fakeClaimData,
middlewareAvailable: true,
},
});

const getIdentitiesWithClaimsSpy = jest.spyOn(claims, 'getIdentitiesWithClaims');

const next = new BigNumber(4);
when(getIdentitiesWithClaimsSpy)
.calledWith({
targets: [target],
claimTypes: [ClaimType.Custom],
includeExpired: false,
})
.mockResolvedValue({
data: [
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: {
type: ScopeType.Identity,
value: someDid,
},
},
},
],
},
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: {
type: ScopeType.Ticker,
value: ticker,
},
},
},
],
},
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: undefined,
},
},
],
},
],
next,
count: new BigNumber(6),
});

when(getIdentitiesWithClaimsSpy)
.calledWith({
targets: [target],
claimTypes: [ClaimType.Custom],
includeExpired: false,
start: next,
})
.mockResolvedValue({
data: [
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: {
type: ScopeType.Identity,
value: someDid,
},
},
},
],
},
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: {
type: ScopeType.Ticker,
value: ticker,
},
},
},
],
},
{
identity: entityMockUtils.getIdentityInstance({ did: someDid }),
claims: [
{
target: entityMockUtils.getIdentityInstance({ did: target }),
issuer: entityMockUtils.getIdentityInstance({ did: someDid }),
issuedAt: new Date(),
lastUpdatedAt: new Date(),
expiry: new Date(),
claim: {
type: ClaimType.Custom,
customClaimTypeId: new BigNumber(1),
scope: undefined,
},
},
],
},
],
next: null,
count: new BigNumber(6),
});

let result = await claims.getClaimScopes({ target });

console.log(result);

expect(result[0].ticker).toBeUndefined();
expect(result[0].scope).toEqual({ type: ScopeType.Identity, value: someDid });
expect(result[2].ticker).toEqual(ticker);
expect(result[2].scope).toEqual({ type: ScopeType.Ticker, value: ticker });

result = await claims.getClaimScopes();

expect(result.length).toEqual(3);
});
});

describe('method: getTargetingClaims', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/api/entities/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ export interface CustomClaim {
customClaimTypeId: BigNumber;
}

export type CustomClaimWithoutScope = Omit<CustomClaim, 'scope'> & { scope: undefined };

export interface BlockedClaim {
type: ClaimType.Blocked;
scope: Scope;
Expand All @@ -232,7 +234,7 @@ export type ScopedClaim =
| BlockedClaim
| CustomClaim;

export type UnscopedClaim = CddClaim;
export type UnscopedClaim = CddClaim | CustomClaimWithoutScope;

export type Claim = ScopedClaim | UnscopedClaim;

Expand Down
1 change: 1 addition & 0 deletions src/api/procedures/modifyClaims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export async function prepareModifyClaims(
const rawExpiry = expiry ? dateToMoment(expiry, context) : null;

allTargets.push(signerToString(target));

modifyClaimArgs.push(
tuple(
stringToIdentityId(signerToString(target), context),
Expand Down
Loading

0 comments on commit 3eeb180

Please sign in to comment.