Skip to content

Commit

Permalink
fix: 🐛 getHistoricalAuthorization for v7 joinIdentity auth
Browse files Browse the repository at this point in the history
fix handling new extrinsic format when transaction permissions are
specified
  • Loading branch information
polymath-eric committed Nov 28, 2024
1 parent bcf40c8 commit 77f172b
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 42 deletions.
41 changes: 40 additions & 1 deletion src/api/procedures/__tests__/inviteAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import BigNumber from 'bignumber.js';
import { when } from 'jest-when';

import { prepareInviteAccount } from '~/api/procedures/inviteAccount';
import { Account, AuthorizationRequest, Context } from '~/internal';
import { Account, AuthorizationRequest, Context, PolymeshError } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import {
Authorization,
AuthorizationType,
ErrorCode,
Identity,
InviteAccountParams,
PermissionType,
ResultSet,
SignerType,
SignerValue,
TxTags,
} from '~/types';
import * as utilsConversionModule from '~/utils/conversion';

Expand Down Expand Up @@ -284,4 +287,40 @@ describe('inviteAccount procedure', () => {
'The target Account already has a pending invitation to join this Identity'
);
});

it('should throw an error if Exclude is specified for transactions', () => {
dsMockUtils.configureMocks({
contextOptions: {
sentAuthorizations: { data: [], next: null },
},
});

entityMockUtils.configureMocks({
accountOptions: {
getIdentity: null,
},
});

permissionsLikeToPermissionsSpy.mockReturnValue({
transactions: { type: PermissionType.Exclude, values: [TxTags.asset.Issue] },
});

const proc = procedureMockUtils.getInstance<InviteAccountParams, AuthorizationRequest>(
mockContext
);

const expectedError = new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Cannot use "Exclude" when specifying permissions',
});

return expect(
prepareInviteAccount.call(proc, {
targetAccount: entityMockUtils.getAccountInstance(),
permissions: {
transactions: { type: PermissionType.Exclude, values: [TxTags.asset.Issue] },
},
})
).rejects.toThrow(expectedError);
});
});
10 changes: 10 additions & 0 deletions src/api/procedures/inviteAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export async function prepareInviteAccount(
authorizationValue = permissionsLikeToPermissions(permissionsLike, context);
}

if (
authorizationValue.transactions &&
authorizationValue.transactions.type === PermissionType.Exclude
) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Cannot use "Exclude" when specifying permissions',
});
}

const authRequest: Authorization = {
type: AuthorizationType.JoinIdentity,
value: authorizationValue,
Expand Down
34 changes: 34 additions & 0 deletions src/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,37 @@ export type CustomTypeData = {
rawValue: Bytes;
isAlreadyCreated?: boolean;
};

/**
* Chain v6 and below permission model
*/
export type MiddlewareV6Extrinsic = Record<
string,
{
palletName: string;
dispatchableNames: Record<string, string[]>;
}[]
>;

export type ExtrinsicGroup =
| {
whole: null;
}
| {
these: string[];
};

/**
* Chain v7 and above permission model
*/
export type MiddlewareV7Extrinsic = {
extrinsic: {
these: {
[key: string]: {
extrinsics: ExtrinsicGroup;
};
};
};
};

export type MiddlewarePermissions = MiddlewareV6Extrinsic | MiddlewareV7Extrinsic;
22 changes: 22 additions & 0 deletions src/utils/__tests__/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10595,6 +10595,28 @@ describe('middlewarePermissionsDataToPermissions', () => {
type: PermissionType.Include,
},
});

permissions = {
asset: { whole: null },
extrinsic: {
these: {
Asset: { extrinsics: { these: ['register_custom_asset_type'] } },
Nft: { extrinsics: { these: ['issue_nft'] } },
},
},
portfolio: { whole: null },
};

result = middlewarePermissionsDataToPermissions(JSON.stringify(permissions), context);
expect(result).toEqual({
assets: null,
portfolios: null,
transactions: {
type: PermissionType.Include,
values: ['asset.registerCustomAssetType', 'nft.issueNft'],
},
transactionGroups: [],
});
});
});

Expand Down
97 changes: 57 additions & 40 deletions src/utils/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,13 @@ import {
CorporateActionIdentifier,
CustomTypeData,
ExemptKey,
ExtrinsicGroup,
ExtrinsicIdentifier,
InstructionStatus as InternalInstructionStatus,
InternalAssetType,
InternalNftType,
MeshTickerOrAssetId,
MiddlewarePermissions,
PalletPermissions,
PalletPermissionsV6,
PalletPermissionsV7,
Expand Down Expand Up @@ -306,6 +308,7 @@ import {
getAssetIdAndTicker,
getAssetIdForMiddleware,
getAssetIdFromMiddleware,
isMiddlewareV6Extrinsic,
isModuleOrTagMatch,
optionize,
padString,
Expand Down Expand Up @@ -5169,58 +5172,72 @@ export function middlewareAgentGroupDataToPermissionGroup(
* @hidden
*/
function middlewareExtrinsicPermissionsDataToTransactionPermissions(
permissions: Record<
string,
{
palletName: string;
dispatchableNames: Record<string, string[]>;
}[]
>
permissions: MiddlewarePermissions
): TransactionPermissions | null {
let extrinsicType: PermissionType;
let pallets;
const isLegacy = isMiddlewareV6Extrinsic(permissions);

let extrinsicType: PermissionType = 'nullish' as unknown as PermissionType;
let rawPallets;
if ('these' in permissions) {
extrinsicType = PermissionType.Include;
pallets = permissions.these;
rawPallets = permissions.these;
} else if ('except' in permissions) {
extrinsicType = PermissionType.Exclude;
pallets = permissions.except;
rawPallets = permissions.except;
}

let txValues: (ModuleName | TxTag)[] = [];
let exceptions: TxTag[] = [];
if (!rawPallets) {
return null;
}

if (pallets) {
pallets.forEach(({ palletName, dispatchableNames }) => {
const moduleName = stringLowerFirst(coerceHexToString(palletName));
if ('except' in dispatchableNames) {
const dispatchables = [...dispatchableNames.except];
exceptions = [
...exceptions,
...dispatchables.map(name => formatTxTag(coerceHexToString(name), moduleName)),
];
txValues = [...txValues, moduleName as ModuleName];
} else if ('these' in dispatchableNames) {
const dispatchables = [...dispatchableNames.these];
txValues = [
...txValues,
...dispatchables.map(name => formatTxTag(coerceHexToString(name), moduleName)),
];
} else {
txValues = [...txValues, moduleName as ModuleName];
}
});
let pallets: {
palletName: string;
dispatchableNames: Record<string, string[]>;
}[] = rawPallets;

const result = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
type: extrinsicType!,
values: txValues,
};
if (!isLegacy) {
pallets = [];

for (const [key, rawBody] of Object.entries(rawPallets)) {
const body = rawBody as unknown as { extrinsics: ExtrinsicGroup };

return exceptions.length ? { ...result, exceptions } : result;
if ('these' in body.extrinsics && body.extrinsics.these) {
pallets.push({ palletName: key, dispatchableNames: { these: body.extrinsics.these } });
}
}
}

return null;
let txValues: (ModuleName | TxTag)[] = [];
let exceptions: TxTag[] = [];

pallets.forEach(({ palletName, dispatchableNames }) => {
const moduleName = stringLowerFirst(coerceHexToString(palletName));
if ('except' in dispatchableNames) {
const dispatchables = [...dispatchableNames.except];
exceptions = [
...exceptions,
...dispatchables.map(name => formatTxTag(coerceHexToString(name), moduleName)),
];
txValues = [...txValues, moduleName as ModuleName];
} else if ('these' in dispatchableNames) {
const dispatchables = [...dispatchableNames.these];
txValues = [
...txValues,
...dispatchables.map(name => formatTxTag(coerceHexToString(name), moduleName)),
];
} else {
txValues = [...txValues, moduleName as ModuleName];
}
});

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
type: extrinsicType!,
values: txValues,
};

return exceptions.length ? { ...result, exceptions } : result;
}

/**
Expand Down
27 changes: 26 additions & 1 deletion src/utils/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ import {
Events,
Falsyable,
MapTxWithArgs,
MiddlewarePermissions,
MiddlewareV6Extrinsic,
PolymeshTx,
Queries,
StatClaimIssuer,
Expand Down Expand Up @@ -2190,7 +2192,7 @@ export function assertNoPendingAuthorizationExists(params: {
throw new PolymeshError({
code: ErrorCode.NoDataChange,
message,
data: { target, issuer, authorizationType, authId },
data: { target, issuer, authorizationType, authId: authId.toString() },
});
}
}
Expand Down Expand Up @@ -2374,3 +2376,26 @@ export async function prepareStorageForCustomType(

return customTypeData;
}

/**
* Determines the middleware permissions follows the legacy format
*/
export function isMiddlewareV6Extrinsic(
permissions: MiddlewarePermissions
): permissions is MiddlewareV6Extrinsic {
const keys = Object.keys(permissions);
const vals = Object.values(permissions);

// API is the same for "whole permissions" or no permissions
if (keys.includes('whole') || vals.length === 0) {
return false;
}

const firstVal = vals[0];

if ('palletName' in firstVal || (firstVal[0] && 'palletName' in firstVal[0])) {
return true;
}

return false;
}

0 comments on commit 77f172b

Please sign in to comment.