diff --git a/js/libs/keycloak-admin-client/src/client.ts b/js/libs/keycloak-admin-client/src/client.ts index 13e45252d1..385efd2fcf 100644 --- a/js/libs/keycloak-admin-client/src/client.ts +++ b/js/libs/keycloak-admin-client/src/client.ts @@ -9,6 +9,7 @@ import { Components } from "./resources/components.js"; import { Groups } from "./resources/groups.js"; import { IdentityProviders } from "./resources/identityProviders.js"; import { Realms } from "./resources/realms.js"; +import { Organizations } from "./resources/organizations.js"; import { Roles } from "./resources/roles.js"; import { ServerInfo } from "./resources/serverInfo.js"; import { Users } from "./resources/users.js"; @@ -34,6 +35,7 @@ export class KeycloakAdminClient { public userStorageProvider: UserStorageProvider; public groups: Groups; public roles: Roles; + public organizations: Organizations; public clients: Clients; public realms: Realms; public clientScopes: ClientScopes; @@ -68,6 +70,7 @@ export class KeycloakAdminClient { this.userStorageProvider = new UserStorageProvider(this); this.groups = new Groups(this); this.roles = new Roles(this); + this.organizations = new Organizations(this); this.clients = new Clients(this); this.realms = new Realms(this); this.clientScopes = new ClientScopes(this); diff --git a/js/libs/keycloak-admin-client/src/defs/organizationDomainRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/organizationDomainRepresentation.ts new file mode 100644 index 0000000000..d4b06fd2f7 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/organizationDomainRepresentation.ts @@ -0,0 +1,4 @@ +export default interface OrganizationDomainRepresentation { + name?: string; + verified?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/organizationRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/organizationRepresentation.ts new file mode 100644 index 0000000000..0639a50162 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/organizationRepresentation.ts @@ -0,0 +1,10 @@ +import type OrganizationDomainRepresentation from "./organizationDomainRepresentation.js"; + +export default interface OrganizationRepresentation { + id?: string; + name?: string; + description?: string; + enabled?: boolean; + attributes?: { [index: string]: string[] }; + domains?: OrganizationDomainRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/resources/organizations.ts b/js/libs/keycloak-admin-client/src/resources/organizations.ts new file mode 100644 index 0000000000..9f706b47d9 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/organizations.ts @@ -0,0 +1,58 @@ +import Resource from "./resource.js"; +import type OrganizationRepresentation from "../defs/organizationRepresentation.js"; + +import type { KeycloakAdminClient } from "../client.js"; + +export interface OrganizationQuery { + first?: number; // The position of the first result to be processed (pagination offset) + max?: number; // The maximum number of results to be returned - defaults to 10 + search?: string; // A String representing either an organization name or domain + q?: string; // A query to search for custom attributes, in the format 'key1:value2 key2:value2' + exact?: boolean; // Boolean which defines whether the param 'search' must match exactly or not +} + +export class Organizations extends Resource<{ realm?: string }> { + /** + * Organizations + */ + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } + + public find = this.makeRequest< + OrganizationQuery, + OrganizationRepresentation[] + >({ + method: "GET", + path: "/organizations", + }); + + public create = this.makeRequest({ + method: "POST", + path: "/organizations", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public delById = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/organizations/{id}", + urlParamKeys: ["id"], + }); + + public updateById = this.makeUpdateRequest< + { id: string }, + OrganizationRepresentation, + void + >({ + method: "PUT", + path: "/organizations/{id}", + urlParamKeys: ["id"], + }); +} diff --git a/js/libs/keycloak-admin-client/test/organizations.spec.ts b/js/libs/keycloak-admin-client/test/organizations.spec.ts new file mode 100644 index 0000000000..c91717ba55 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/organizations.spec.ts @@ -0,0 +1,56 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Organizations", () => { + let kcAdminClient: KeycloakAdminClient; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + }); + + it("retrieves empty organizations list", async () => { + const organizations = await kcAdminClient.organizations.find(); + expect(organizations).to.be.ok; + expect(organizations).to.be.empty; + }); + + it("creates, updates, and removes an organization", async () => { + const myOrganization = { + name: "orga", + enabled: true, + domains: [ + { + name: "orga.com", + }, + ], + }; + + const org = await kcAdminClient.organizations.create(myOrganization); + let allOrganizations = await kcAdminClient.organizations.find(); + expect(allOrganizations).to.be.ok; + expect(allOrganizations).to.be.not.empty; + + myOrganization.enabled = false; + await kcAdminClient.organizations.updateById( + { id: org.id }, + myOrganization, + ); + + allOrganizations = await kcAdminClient.organizations.find(); + expect(allOrganizations).to.be.ok; + expect(allOrganizations.length).to.equal(1); + expect(allOrganizations[0].enabled).to.be.false; + + await kcAdminClient.organizations.delById({ + id: org.id, + }); + allOrganizations = await kcAdminClient.organizations.find(); + expect(allOrganizations).to.be.ok; + expect(allOrganizations).to.be.empty; + }); +});