Skip to content

Commit

Permalink
feat(identity): add hasTokenPermissions
Browse files Browse the repository at this point in the history
Also deprecated some roles and eliminated others

BREAKING CHANGE: `RoleType.TokenOwner` no longer exists, and as a consequence neither does the
`TokenOwnerRole`
  • Loading branch information
monitz87 committed Jul 8, 2021
1 parent 612430a commit 5b8873d
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 71 deletions.
148 changes: 127 additions & 21 deletions src/api/entities/Identity/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { u64 } from '@polkadot/types';
import { BigNumber } from 'bignumber.js';
import P from 'bluebird';
import { chunk, flatten, uniqBy } from 'lodash';
import { CddStatus, DidRecord, Instruction as MeshInstruction } from 'polymesh-types/types';
import { chunk, flatten, intersectionBy, uniqBy } from 'lodash';
import {
CddStatus,
DidRecord,
Instruction as MeshInstruction,
ModuleName,
TxTag,
TxTags,
} from 'polymesh-types/types';

import { assertPortfolioExists } from '~/api/procedures/utils';
import {
Expand All @@ -28,6 +35,7 @@ import {
isTokenPiaRole,
isVenueOwnerRole,
Order,
PermissionType,
ResultSet,
Role,
SubCallback,
Expand All @@ -41,6 +49,7 @@ import {
boolToBoolean,
cddStatusToBoolean,
corporateActionIdentifierToCaId,
extrinsicPermissionsToTransactionPermissions,
identityIdToString,
portfolioIdToMeshPortfolioId,
portfolioIdToPortfolio,
Expand All @@ -50,7 +59,7 @@ import {
stringToTicker,
u64ToBigNumber,
} from '~/utils/conversion';
import { calculateNextKey, getTicker, removePadding } from '~/utils/internal';
import { calculateNextKey, getTicker, isModuleOrTagMatch, removePadding } from '~/utils/internal';

import { IdentityAuthorizations } from './IdentityAuthorizations';
import { Portfolios } from './Portfolios';
Expand Down Expand Up @@ -99,18 +108,129 @@ export class Identity extends Entity<UniqueIdentifiers> {
}

/**
* Check whether this Identity possesses the specified Role
* Check whether this Identity has specific transaction Permissions over a Security Token
*/
public async hasRole(role: Role): Promise<boolean> {
public async hasTokenPermissions(args: {
token: SecurityToken | string;
transactions: TxTag[] | null;
}): Promise<boolean> {
const {
context,
context: {
polymeshApi: {
query: { externalAgents },
},
},
context,
did,
} = this;
const { token, transactions } = args;

const ticker = getTicker(token);
const rawTicker = stringToTicker(ticker, context);

const groupOption = await externalAgents.groupOfAgent(
rawTicker,
stringToIdentityId(did, context)
);

if (groupOption.isNone) {
return false;
}

const group = groupOption.unwrap();

if (group.isFull) {
return true;
}

if (transactions === null) {
return false;
}

/*
* Not authorized:
* - externalAgents
* - identity.acceptAuthorization
*/
if (group.isExceptMeta) {
return !transactions.some(
tag =>
tag.split('.')[0] === ModuleName.ExternalAgents ||
tag === TxTags.identity.AcceptAuthorization
);
}

/*
* Authorized:
* - asset.issue
* - asset.redeem
* - asset.controllerTransfer
* - sto (except for sto.invest)
*/
if (group.isPolymeshV1Pia) {
return transactions.every(tag => {
const isSto = tag.split('.')[0] === ModuleName.Sto && tag !== TxTags.sto.Invest;
const isAsset = [TxTags.asset.Issue, TxTags.asset.Redeem, TxTags.asset.ControllerTransfer];

return isSto || isAsset;
});
}

/*
* Authorized:
* - corporateAction
* - corporateBallot
* - capitalDistribution
*/
if (group.isPolymeshV1Caa) {
return transactions.every(tag =>
[
ModuleName.CorporateAction,
ModuleName.CorporateBallot,
ModuleName.CapitalDistribution,
].includes(tag.split('.')[0] as ModuleName)
);
}

const groupId = group.asCustom;

const groupPermissionsOption = await externalAgents.groupPermissions(rawTicker, groupId);

const permissions = extrinsicPermissionsToTransactionPermissions(
groupPermissionsOption.unwrap()
);

if (permissions === null) {
return true;
}

const { type, exceptions, values } = permissions;

/*
* if type is include:
* all passed tags are in the values array AND are not in the exceptions array (isInValues && !isInExceptions)
* if type is exclude:
* all passed tags are not in the values array OR are in the exceptions array (!isInValues || isInExceptions)
*/
const isPresent = (tag: TxTag, flipResult: boolean) => {
const isInValues = values.some(value => isModuleOrTagMatch(value, tag));
const isInExceptions = !!exceptions?.includes(tag);

const result = isInValues && !isInExceptions;

return flipResult ? result : !result;
};

const isInclude = type === PermissionType.Include;

return transactions.every(tag => isPresent(tag, isInclude));
}

/**
* Check whether this Identity possesses the specified Role
*/
public async hasRole(role: Role): Promise<boolean> {
const { context, did } = this;

if (isTickerOwnerRole(role)) {
const { ticker } = role;
Expand All @@ -119,27 +239,13 @@ export class Identity extends Entity<UniqueIdentifiers> {
const { owner } = await reservation.details();

return owner?.did === did;
} else if (isTokenOwnerRole(role)) {
const { ticker } = role;

const rawTicker = stringToTicker(ticker, context);
const rawIdentityId = stringToIdentityId(did, context);

const groupOfAgent = await externalAgents.groupOfAgent(rawTicker, rawIdentityId);

if (groupOfAgent.isSome) {
const agentGroup = groupOfAgent.unwrap();
return agentGroup.isFull;
}

return false;
} else if (isTokenPiaRole(role)) {
const { ticker } = role;

const token = new SecurityToken({ ticker }, context);
const { primaryIssuanceAgents } = await token.details();

return primaryIssuanceAgents.map(({ did: agentDid }) => agentDid).includes(did);
return !!primaryIssuanceAgents.find(({ did: agentDid }) => agentDid === did);
} else if (isTokenCaaRole(role)) {
const { ticker } = role;

Expand Down
23 changes: 9 additions & 14 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export enum TransactionQueueStatus {

export enum RoleType {
TickerOwner = 'TickerOwner',
TokenOwner = 'TokenOwner',
TokenFullAgent = 'TokenFullAgent',
TokenPia = 'TokenPia',
TokenCaa = 'TokenCaa',
CddProvider = 'CddProvider',
Expand All @@ -96,16 +96,17 @@ export interface TickerOwnerRole {
ticker: string;
}

export interface TokenOwnerRole {
type: RoleType.TokenOwner;
ticker: string;
}

/**
* @deprecated in favor of external agent permissions
*/
export interface TokenPiaRole {
type: RoleType.TokenPia;
ticker: string;
}

/**
* @deprecated in favor of external agent permissions
*/
export interface TokenCaaRole {
type: RoleType.TokenCaa;
ticker: string;
Expand All @@ -127,7 +128,6 @@ export interface PortfolioCustodianRole {

export type Role =
| TickerOwnerRole
| TokenOwnerRole
| TokenPiaRole
| TokenCaaRole
| CddProviderRole
Expand Down Expand Up @@ -157,25 +157,20 @@ export function isCddProviderRole(role: Role): role is CddProviderRole {

/**
* @hidden
* @deprecated
*/
export function isTokenCaaRole(role: Role): role is TokenCaaRole {
return role.type === RoleType.TokenCaa;
}

/**
* @hidden
* @deprecated
*/
export function isTokenPiaRole(role: Role): role is TokenPiaRole {
return role.type === RoleType.TokenPia;
}

/**
* @hidden
*/
export function isTokenOwnerRole(role: Role): role is TokenOwnerRole {
return role.type === RoleType.TokenOwner;
}

/**
* @hidden
*/
Expand Down
84 changes: 48 additions & 36 deletions src/utils/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
DocumentType,
DocumentUri,
EcdsaSignature,
ExtrinsicPermissions,
FundingRoundName,
Fundraiser,
FundraiserName,
Expand Down Expand Up @@ -890,44 +891,17 @@ export function permissionsToMeshPermissions(
/**
* @hidden
*/
export function meshPermissionsToPermissions(
permissions: MeshPermissions,
context: Context
): Permissions {
const { asset, extrinsic, portfolio } = permissions;

let tokens: SectionPermissions<SecurityToken> | null = null;
let transactions: TransactionPermissions | null = null;
let portfolios: SectionPermissions<DefaultPortfolio | NumberedPortfolio> | null = null;

let tokensType: PermissionType;
let securityTokens;
if (asset.isThese) {
tokensType = PermissionType.Include;
securityTokens = asset.asThese;
} else if (asset.isExcept) {
tokensType = PermissionType.Exclude;
securityTokens = asset.asExcept;
}

if (securityTokens) {
tokens = {
values: securityTokens.map(
ticker => new SecurityToken({ ticker: tickerToString(ticker) }, context)
),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
type: tokensType!,
};
}

export function extrinsicPermissionsToTransactionPermissions(
permissions: ExtrinsicPermissions
): TransactionPermissions | null {
let extrinsicType: PermissionType;
let pallets;
if (extrinsic.isThese) {
if (permissions.isThese) {
extrinsicType = PermissionType.Include;
pallets = extrinsic.asThese;
} else if (extrinsic.isExcept) {
pallets = permissions.asThese;
} else if (permissions.isExcept) {
extrinsicType = PermissionType.Exclude;
pallets = extrinsic.asExcept;
pallets = permissions.asExcept;
}

let txValues: (ModuleName | TxTag)[] = [];
Expand All @@ -952,14 +926,52 @@ export function meshPermissionsToPermissions(
}
});

transactions = {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
type: extrinsicType!,
values: uniq(txValues),
values: txValues,
exceptions,
};
}

return null;
}

/**
* @hidden
*/
export function meshPermissionsToPermissions(
permissions: MeshPermissions,
context: Context
): Permissions {
const { asset, extrinsic, portfolio } = permissions;

let tokens: SectionPermissions<SecurityToken> | null = null;
let transactions: TransactionPermissions | null = null;
let portfolios: SectionPermissions<DefaultPortfolio | NumberedPortfolio> | null = null;

let tokensType: PermissionType;
let securityTokens;
if (asset.isThese) {
tokensType = PermissionType.Include;
securityTokens = asset.asThese;
} else if (asset.isExcept) {
tokensType = PermissionType.Exclude;
securityTokens = asset.asExcept;
}

if (securityTokens) {
tokens = {
values: securityTokens.map(
ticker => new SecurityToken({ ticker: tickerToString(ticker) }, context)
),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
type: tokensType!,
};
}

transactions = extrinsicPermissionsToTransactionPermissions(extrinsic);

let portfoliosType: PermissionType;
let portfolioIds;
if (portfolio.isThese) {
Expand Down

0 comments on commit 5b8873d

Please sign in to comment.