Skip to content

Commit

Permalink
Feat: Identity Hub participant and participants implementation (#351)
Browse files Browse the repository at this point in the history
* chore: download multiple openapi specs

* feat: identity hub participant and participants contollers

* fix: review requests

* fix: testcontainer file name

* fix: add base path constant

* fix: pull request comments
  • Loading branch information
mohamed-Dhia authored Jan 27, 2025
1 parent a8e3f77 commit eba0c0e
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 18 deletions.
40 changes: 23 additions & 17 deletions pretest/edc-openapi.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
const fs = require('fs/promises');
const fs = require(`fs/promises`);

const folder = "node_modules";

const context = "management-api";
const resourceUrl = `https://eclipse-edc.github.io/Connector/openapi/${context}/${context}.yaml`;
const CONTEXTS = [
{
name: "management-api",
resourceUrl: "https://eclipse-edc.github.io/Connector/openapi/management-api/management-api.yaml"
},
{
name: "identity-api",
resourceUrl: "https://eclipse-edc.github.io/IdentityHub/openapi/identity-api/identity-api.yaml"
}
]

fetch(resourceUrl)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to download ${context} openapi spec`);
}
return response.text()
})
.then(text => text
.replaceAll("example: null", "")
.replaceAll("https://w3id.org/edc/v0.0.1/ns/value", "value")) // to make "secrets" test working, will be fixed in the future
.then(text => fs.writeFile(`${folder}/${context}.yml`, text))
.catch(error => {
console.error('Error downloading openapi specs:', error);
})
Promise.all(CONTEXTS.map((context) =>
fetch(context.resourceUrl)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to download ${context.name} openapi spec`);
}
return response.text()
})
.then(text => fs.writeFile(`${folder}/${context.name}.yml`, text))
.catch(error => {
console.error(`Error downloading openapi specs for ${context.name}:`, error);
})))
14 changes: 14 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Addresses } from "./entities";
import { ManagementController } from "./facades/management";
import { Inner } from "./inner";
import { FederatedCatalogController } from "./controllers/federated-catalog-controller";
import { IdentityController } from "./facades/identity";

export type EdcConnectorClientType<T extends Record<string, EdcController>> =
EdcConnectorClient & T;
Expand All @@ -28,6 +29,11 @@ class Builder<T extends Record<string, EdcController> = {}> {
return this;
}

identityUrl(identityUrl: string): this {
this.#instance[addressesSymbol].identity = identityUrl;
return this;
}

defaultUrl(defaultUrl: string): this {
this.#instance[addressesSymbol].default = defaultUrl;
return this;
Expand Down Expand Up @@ -90,6 +96,14 @@ export class EdcConnectorClient {
return new ManagementController(this[innerSymbol], context);
}

get identity() {
const context = new EdcConnectorClientContext(
this[apiTokenSymbol],
this[addressesSymbol],
);
return new IdentityController(this[innerSymbol], context);
}

get observability() {
const context = new EdcConnectorClientContext(
this[apiTokenSymbol],
Expand Down
4 changes: 4 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export class EdcConnectorClientContext implements Addresses {
return this.getOrError(this.#addresses.default, "default address");
}

get identity(): string {
return this.getOrError(this.#addresses.identity, "identity address");
}

get protocol(): string {
return this.getOrError(this.#addresses.protocol, "protocol address");
}
Expand Down
86 changes: 86 additions & 0 deletions src/controllers/identity-controllers/participant-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { EdcConnectorClientContext } from "../../context";
import {
ParticipantInput,
ParticipantRoleResponse,
} from "../../entities/participant";
import { Inner } from "../../inner";

export class ParticipantController {
#inner: Inner;
#context?: EdcConnectorClientContext;
static readonly BASE_PATH = "/v1alpha/participants";

constructor(
inner: Inner,
public participantId: string,
context?: EdcConnectorClientContext,
) {
this.#inner = inner;
this.#context = context;
}

async delete(context?: EdcConnectorClientContext) {
const actualContext = context || this.#context!;

return this.#inner.request<string>(actualContext.identity, {
path: `${ParticipantController.BASE_PATH}/${this.participantId}`,
method: "DELETE",
apiToken: actualContext.apiToken,
});
}

updateRoles(roles: string[], context?: EdcConnectorClientContext) {
const actualContext = context || this.#context!;

return this.#inner.request<ParticipantRoleResponse[]>(
actualContext.identity,
{
path: `${ParticipantController.BASE_PATH}/${this.participantId}/roles`,
method: "PUT",
body: roles,
apiToken: actualContext.apiToken,
},
);
}

updateState(
isActive: boolean,
input: Omit<ParticipantInput, "participantId">,
context?: EdcConnectorClientContext,
) {
const actualContext = context || this.#context!;
// to be fixed in docs
const body: ParticipantInput = {
...input,
participantId: this.participantId,
};

return this.#inner.request<string>(actualContext.identity, {
path: `${ParticipantController.BASE_PATH}/${this.participantId}/state`,
method: "POST",
query: { isActive: String(isActive) },
body,
apiToken: actualContext.apiToken,
});
}

regenerateToken(
input: Omit<ParticipantInput, "participantId">,
context?: EdcConnectorClientContext,
) {
const actualContext = context || this.#context!;

// to be fixed in docs
const body: ParticipantInput = {
...input,
participantId: this.participantId,
};

return this.#inner.request<string>(actualContext.identity, {
path: `${ParticipantController.BASE_PATH}/${this.participantId}/token`,
method: "POST",
body,
apiToken: actualContext.apiToken,
});
}
}
53 changes: 53 additions & 0 deletions src/controllers/identity-controllers/participants-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EdcConnectorClientContext } from "../../context";
import { Participant, ParticipantInput } from "../../entities/participant";
import { Inner } from "../../inner";

export class ParticipantsController {
#inner: Inner;
#context?: EdcConnectorClientContext;
static readonly BASE_PATH = "/v1alpha/participants";

constructor(inner: Inner, context?: EdcConnectorClientContext) {
this.#inner = inner;
this.#context = context;
}

async queryAll(
query: { offset?: string; limit?: string } = {},
context?: EdcConnectorClientContext,
) {
const actualContext = context || this.#context!;

return this.#inner.request<Participant[]>(actualContext.identity, {
path: ParticipantsController.BASE_PATH,
method: "GET",
apiToken: actualContext.apiToken,
query,
});
}

async create(input: ParticipantInput, context?: EdcConnectorClientContext) {
const actualContext = context || this.#context!;

return this.#inner.request<{
apiKey: string;
clientId: string;
clientSecret: string;
}>(actualContext.identity, {
path: ParticipantsController.BASE_PATH,
method: "POST",
apiToken: actualContext.apiToken,
body: input,
});
}

async get(participantId: number, context?: EdcConnectorClientContext) {
const actualContext = context || this.#context!;

return this.#inner.request<Participant>(actualContext.identity, {
path: `${ParticipantsController.BASE_PATH}/${participantId}`,
method: "GET",
apiToken: actualContext.apiToken,
});
}
}
1 change: 1 addition & 0 deletions src/entities/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface Addresses {
public?: string;
control?: string;
federatedCatalogUrl?: string;
identity?: string;
}
45 changes: 45 additions & 0 deletions src/entities/participant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export interface Participant {
apiTokenAlias: string;
createdAt: number;
did: string;
lastModified: number;
participantContextId: string;
roles: string[];
state: number;
}

export interface ParticipantInput {
active: boolean;
additionalProperties: {
[key: string]: unknown;
};
did: string;
key: {
active: boolean;
keyGeneratorParams: {
[key: string]: unknown;
};
keyId: string;
privateKeyAlias: string;
publicKeyJwk: {
[key: string]: unknown;
};
publicKeyPem: string;
resourceId: string;
type: string;
};
participantId: string;
roles: string[];
serviceEndpoints: {
id: string;
serviceEndpoint: string;
type: string;
}[];
}

export interface ParticipantRoleResponse {
invalidValue: Record<string, unknown>;
message: string;
path: string;
type: string;
}
13 changes: 13 additions & 0 deletions src/facades/identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ParticipantController } from "../controllers/identity-controllers/participant-controller";
import { ParticipantsController } from "../controllers/identity-controllers/participants-controller";
import { EdcController } from "../edc-controller";

export class IdentityController extends EdcController {
get participants() {
return new ParticipantsController(this.inner, this.context);
}

participant(participantId: string) {
return new ParticipantController(this.inner, participantId, this.context);
}
}
Loading

0 comments on commit eba0c0e

Please sign in to comment.