Skip to content

Commit

Permalink
Merge pull request #182 from energywebfoundation/feature/SWTCH-905_re…
Browse files Browse the repository at this point in the history
…quest_on_chain_role

feature/SWTCH-905 request on-chain role
  • Loading branch information
JGiter committed Jun 7, 2021
2 parents cd06dc3 + bb52568 commit b891505
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 46 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@energyweb/iam-contracts": "1.10.1",
"@energyweb/iam-contracts": "1.11.0",
"@ensdomains/ens": "^0.4.5",
"@ew-did-registry/claims": "0.5.2-alpha.1045.0",
"@ew-did-registry/did": "0.5.2-alpha.1045.0",
Expand Down
2 changes: 1 addition & 1 deletion src/cacheServerClient/cacheServerClient.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export interface Profile {
export interface ClaimData extends Record<string, unknown> {
profile?: Profile;
claimType?: string;
claimTypeVersion?: string;
claimTypeVersion?: number;
}

export enum Order {
Expand Down
3 changes: 2 additions & 1 deletion src/errors/ErrorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export enum ERROR_MESSAGES {
METAMASK_EXTENSION_NOT_AVAILABLE = "Selected Metamask provider but Metamask not available",
ROLE_PRECONDITION_NOT_MET = "Precondition not met, user not eligible to enrol for a role",
ROLE_NOT_EXISTS = "Role you want to enroll to does not exists",
CLAIM_PUBLISHER_NOT_REQUESTER = "Claim subject is not controlled by publisher"
CLAIM_PUBLISHER_NOT_REQUESTER = "Claim subject is not controlled by publisher",
ONCHAIN_ROLE_VERSION_NOT_SPECIFIED = "On-chain role version not specified"
}
123 changes: 90 additions & 33 deletions src/iam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// @authors: Kim Honoridez
// @authors: Daniel Wojno

import { providers, Signer } from "ethers";
import { providers, Signer, utils } from "ethers";
import {
IRoleDefinition,
IAppDefinition,
Expand Down Expand Up @@ -64,6 +64,8 @@ import { addressOf } from "@ew-did-registry/did-ethr-resolver";
import { isValidDID } from "./utils/did";
import { chainConfigs } from "./iam/chainConfig";

const { id, keccak256, defaultAbiCoder, solidityKeccak256, arrayify } = utils;

export type InitializeData = {
did: string | undefined;
connected: boolean;
Expand All @@ -81,6 +83,8 @@ export interface IMessage {

export interface IClaimRequest extends IMessage {
token: string;
registrationTypes: RegistrationTypes[];
agreement?: string;
}

export interface IClaimIssuance extends IMessage {
Expand All @@ -98,6 +102,11 @@ export enum ENSNamespaceTypes {
Organization = "org"
}

export enum RegistrationTypes {
OffChain = "RegistrationTypes::OffChain",
OnChain = "RegistrationTypes::OnChain"
}

/**
* Decentralized Identity and Access Management (IAM) Type
*/
Expand Down Expand Up @@ -1337,17 +1346,31 @@ export class IAM extends IAMBase {

// NATS

private verifyEnrolmentPreconditions({
claims,
enrolmentPreconditions
private async verifyEnrolmentPreconditions({
subject,
role
}: {
claims: (IServiceEndpoint & ClaimData)[];
enrolmentPreconditions: IRoleDefinition["enrolmentPreconditions"];
subject: string;
role: string;
}) {
const [roleDefinition, { service }] = await Promise.all([
this.getDefinition({
type: ENSNamespaceTypes.Roles,
namespace: role
}),
this.getDidDocument({ did: subject, includeClaims: true })
]);

if (!roleDefinition) {
throw new Error(ERROR_MESSAGES.ROLE_NOT_EXISTS);
}

const { enrolmentPreconditions } = roleDefinition as IRoleDefinition;

if (!enrolmentPreconditions || enrolmentPreconditions.length < 1) return;
for (const { type, conditions } of enrolmentPreconditions) {
if (type === PreconditionType.Role && conditions && conditions?.length > 0) {
const conditionMet = claims.some(
const conditionMet = service.some(
({ claimType }) => claimType && conditions.includes(claimType)
);
if (!conditionMet) {
Expand All @@ -1357,58 +1380,92 @@ export class IAM extends IAMBase {
}
}

private async approveRolePublishing({ subject, role, version }: { subject: string, role: string, version: number }) {
if (!this._signer) {
throw new Error(ERROR_MESSAGES.SIGNER_NOT_INITIALIZED);
}

const erc712_type_hash = id("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
const agreement_type_hash = id("Agreement(address subject,bytes32 role,uint256 version)");

const domainSeparator = keccak256(
defaultAbiCoder.encode(
["bytes32", "bytes32", "bytes32", "uint256", "address"],
[erc712_type_hash, id("Claim Manager"), id("1.0"), (await this._provider.getNetwork()).chainId, this._claimManager.address]
)
);

const typedMsgPrefix = "1901";
const messageId = Buffer.from(typedMsgPrefix, "hex");

const agreementHash = solidityKeccak256(
["bytes", "bytes32", "bytes32"],
[
messageId,
domainSeparator,
keccak256(defaultAbiCoder.encode(
["bytes32", "address", "bytes32", "uint256"],
[agreement_type_hash, subject, namehash(role), version]
))
]
);

return this._signer.signMessage(arrayify(
agreementHash
));
}

async createClaimRequest({
issuer,
claim,
subject
subject,
registrationTypes = [RegistrationTypes.OffChain]
}: {
issuer: string[];
claim: { claimType: string; fields: { key: string; value: string | number }[] };
claim: { claimType: string; claimTypeVersion: number; fields: { key: string; value: string | number }[] };
subject?: string;
registrationTypes?: RegistrationTypes[];
}) {
if (!this._did) {
throw new Error(ERROR_MESSAGES.USER_NOT_LOGGED_IN);
}
if (!subject) {
subject = this._did;
}
const { claimType: role, claimTypeVersion: version } = claim;
const token = await this.createPublicClaim({ data: claim, subject });

const [roleDefinition, didDocument] = await Promise.all([
this.getDefinition({
type: ENSNamespaceTypes.Roles,
namespace: claim.claimType
}),
this.getDidDocument({ did: subject, includeClaims: true })
]);

if (!roleDefinition) {
throw new Error(ERROR_MESSAGES.ROLE_NOT_EXISTS);
// TODO: verfiy onchain
if (registrationTypes.includes(RegistrationTypes.OffChain)) {
await this.verifyEnrolmentPreconditions({ subject, role });
}

const { enrolmentPreconditions } = roleDefinition as IRoleDefinition;

this.verifyEnrolmentPreconditions({ claims: didDocument.service, enrolmentPreconditions });

const token = await this.createPublicClaim({ data: claim, subject });
const message: IClaimRequest = {
id: uuid(),
token,
claimIssuer: issuer,
requester: this._did,
registrationTypes
};

if (!this._natsConnection) {
if (this._cacheClient) {
return this._cacheClient.requestClaim({ did: subject, message });
if (registrationTypes.includes(RegistrationTypes.OnChain)) {
if (!version) {
throw new Error(ERROR_MESSAGES.ONCHAIN_ROLE_VERSION_NOT_SPECIFIED);
}
throw new NATSConnectionNotEstablishedError();
message.agreement = await this.approveRolePublishing({ subject, role, version });
}

const data = this._jsonCodec?.encode(message);

issuer.map(issuerDID => {
this._natsConnection?.publish(`${issuerDID}.${NATS_EXCHANGE_TOPIC}`, data);
});
if (this._natsConnection) {
issuer.map((issuerDID) =>
this._natsConnection?.publish(
`${issuerDID}.${NATS_EXCHANGE_TOPIC}`,
this._jsonCodec?.encode(message)
));
} else if (this._cacheClient) {
await this._cacheClient.requestClaim({ did: subject, message });
} else {
throw new NATSConnectionNotEstablishedError();
}
}

async issueClaimRequest({
Expand Down
6 changes: 5 additions & 1 deletion src/iam/chainConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ChainConfig {
domainNotifierAddress: string;
assetManagerAddress: string;
didContractAddress: string;
claimManagerAddress: string;
}

export interface MessagingOptions {
Expand All @@ -35,8 +36,11 @@ export const chainConfigs: Record<number, ChainConfig> = {
ensResolverAddress: VOLTA_RESOLVER_V1_ADDRESS,
ensPublicResolverAddress: VOLTA_PUBLIC_RESOLVER_ADDRESS,
domainNotifierAddress: VOLTA_DOMAIN_NOTIFER_ADDRESS,
// TODO: export from did-lib
assetManagerAddress: "0xE258fA7D1cc8964D0dEB7204Df947bCa42b2c940",
didContractAddress: VoltaAddress1056
didContractAddress: VoltaAddress1056,
// TODO: export from iam-contracts
claimManagerAddress: "0x2F259e307D0Ba78902391c070e7b4aA043E74DBB"
}
};

Expand Down
6 changes: 6 additions & 0 deletions src/iam/iam-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Methods } from "@ew-did-registry/did";
import { DIDDocumentFull } from "@ew-did-registry/did-document";
import { ClaimsIssuer, ClaimsUser, ClaimsVerifier } from "@ew-did-registry/claims";
import { DidStore } from "@ew-did-registry/did-ipfs-store";
import { ClaimManager } from "@energyweb/iam-contracts/dist";
import { EnsRegistryFactory } from "../../ethers/EnsRegistryFactory";
import { EnsRegistry } from "../../ethers/EnsRegistry";
import { JWT } from "@ew-did-registry/jwt";
Expand Down Expand Up @@ -101,6 +102,7 @@ export class IAMBase {
protected _ensRegistryAddress: string;

protected _assetManager: IdentityManager;
protected _claimManager: ClaimManager;

protected _cacheClient: ICacheServerClient;

Expand Down Expand Up @@ -604,6 +606,7 @@ export class IAMBase {
domainNotifierAddress,
didContractAddress,
assetManagerAddress,
claimManagerAddress
} = chainConfigs[chainId];

if (!ensRegistryAddress)
Expand All @@ -616,6 +619,9 @@ export class IAMBase {
throw new Error(
`Chain config for chainId: ${chainId} does not contain assetManagerContractAddress`
);
if (!claimManagerAddress) {
throw new Error(`Chain config for chainId: ${chainId} does not contain claimManagerAddress`);
}
if (!this._signer) {
throw new Error(ERROR_MESSAGES.SIGNER_NOT_INITIALIZED);
}
Expand Down
8 changes: 4 additions & 4 deletions test/claims.testSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const claimsTests = () => {

test("ens claim can be created", async () => {
await iam.createSelfSignedClaim({
data: { claimType: namespace, claimTypeVersion: "1" }
data: { claimType: namespace, claimTypeVersion: 1 }
});
const { service = [] } = await iam.getDidDocument();
expect(service.find(({ claimType }) => claimType === namespace)).toBeTruthy();
Expand Down Expand Up @@ -39,17 +39,17 @@ export const claimsTests = () => {
});
test("ens claim should be created when claimTypeVersion do not match", async () => {
await iam.createSelfSignedClaim({
data: { claimType: namespace, claimTypeVersion: "2" }
data: { claimType: namespace, claimTypeVersion: 2 }
});
const { service: newServices = [] } = await iam.getDidDocument();
expect(
newServices.find(claim => claim.claimType === namespace && claim.claimTypeVersion === "2")
newServices.find(claim => claim.claimType === namespace && claim.claimTypeVersion === 2)
).toBeTruthy();
expect(newServices.length).toBe(3);
});
test("ens claim should not be created", async () => {
await iam.createSelfSignedClaim({
data: { claimType: namespace, claimTypeVersion: "2" }
data: { claimType: namespace, claimTypeVersion: 2 }
});
const { service: newServices = [] } = await iam.getDidDocument();
expect(newServices.length).toBe(3);
Expand Down
6 changes: 4 additions & 2 deletions test/iam.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
didContract,
GANACHE_PORT,
assetsManager,
domainNotifer
domainNotifer,
claimManager
} from "./setup_contracts";
import { labelhash } from "../src/utils/ENS_hash";
import { orgTests } from "./organization.testSuite";
Expand Down Expand Up @@ -42,7 +43,8 @@ beforeAll(async () => {
ensResolverAddress: ensResolver.address,
didContractAddress: didContract.address,
assetManagerAddress: assetsManager.address,
domainNotifierAddress: domainNotifer.address
domainNotifierAddress: domainNotifer.address,
claimManagerAddress: claimManager.address
});
setCacheClientOptions(9, { url: "" });

Expand Down
5 changes: 5 additions & 0 deletions test/setup_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { EnsRegistryFactory } from "../ethers/EnsRegistryFactory";
import { IdentityManagerFactory } from "../ethers/IdentityManagerFactory";
import { IdentityManager } from "../ethers/IdentityManager";
import { OfferableIdentityFactory } from "../ethers/OfferableIdentityFactory";
import { ClaimManager__factory } from "@energyweb/iam-contracts/";
import { ClaimManager } from "@energyweb/iam-contracts/dist/ethers-v4/ClaimManager";

const { JsonRpcProvider } = providers;
const { parseEther } = utils;
Expand All @@ -22,6 +24,7 @@ export let ensResolver: RoleDefinitionResolver;
export let domainNotifer: DomainNotifier;
export let didContract: Contract;
export let assetsManager: IdentityManager;
export let claimManager: ClaimManager;

export const deployContracts = async (privateKey: string): Promise<void> => {
const wallet = new Wallet(privateKey, provider);
Expand All @@ -35,6 +38,8 @@ export const deployContracts = async (privateKey: string): Promise<void> => {
const identityFactory = new OfferableIdentityFactory(wallet);
const library = await identityFactory.deploy();
assetsManager = await new IdentityManagerFactory(wallet).deploy(library.address);

claimManager = await new ClaimManager__factory(wallet).deploy(didContract.address, ensRegistry.address);
};

export const replenish = async (acc: string) => {
Expand Down

0 comments on commit b891505

Please sign in to comment.