From 4cc01ed771887be12f2482a5f009b59f92be44c0 Mon Sep 17 00:00:00 2001 From: Hugo Renard Date: Mon, 25 Apr 2022 12:42:30 +0200 Subject: [PATCH] fix role mapping + create role if doesn't exit --- src/endpoints/ScimEndpoint.ts | 15 ++--------- src/endpoints/UserEndpoint.ts | 4 ++- src/endpoints/UsersEndpoint.ts | 4 ++- src/endpoints/common/endpoint.ts | 14 ++++++++++ src/endpoints/common/user.ts | 25 ++++++++++++++++++ src/rc-sdk/RcSdk.ts | 7 +++-- src/rc-sdk/RcSdkRole.ts | 44 ++++++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 src/endpoints/common/endpoint.ts create mode 100644 src/endpoints/common/user.ts create mode 100644 src/rc-sdk/RcSdkRole.ts diff --git a/src/endpoints/ScimEndpoint.ts b/src/endpoints/ScimEndpoint.ts index 745fdb0..f5f1bbf 100644 --- a/src/endpoints/ScimEndpoint.ts +++ b/src/endpoints/ScimEndpoint.ts @@ -18,6 +18,7 @@ import { EmptyRequestError } from "../errors/EmptyRequestError"; import { EmptyResponseError } from "../errors/EmptyResponseError"; import { JsonParseError } from "../errors/JsonParseError"; import { SCIMError, SCIMErrorType } from "../scim/Error"; +import { handleRcError } from "./common/endpoint"; import { Context } from "./Context"; type ApiEndpointMethod = ( @@ -41,6 +42,7 @@ export abstract class ScimEndpoint extends ApiEndpoint { public post: ApiEndpointMethod | undefined; public put: ApiEndpointMethod | undefined; public delete: ApiEndpointMethod | undefined; + protected handleError = handleRcError; constructor(app: IApp) { super(app); @@ -91,19 +93,6 @@ export abstract class ScimEndpoint extends ApiEndpoint { } } - protected handleError(o: any) { - if (!o.success) { - if (o.error?.includes("already in use")) { - throw new ConflictError( - o.error.includes("@") ? "email" : "username", - ); - } - if (o.error?.includes("not found")) { - } - throw new Error(o.error); - } - } - private wrapMethod(name: string): ApiEndpointMethod | undefined { const method = this[`_${name}`]; if (method === undefined || typeof method !== "function") { diff --git a/src/endpoints/UserEndpoint.ts b/src/endpoints/UserEndpoint.ts index 7173af6..58bcdbb 100644 --- a/src/endpoints/UserEndpoint.ts +++ b/src/endpoints/UserEndpoint.ts @@ -2,6 +2,7 @@ import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors"; import { IApiResponse } from "@rocket.chat/apps-engine/definition/api"; import { SCIMError, SCIMErrorType } from "../scim/Error"; import { SCIMUser } from "../scim/User"; +import { findOrCreateRole } from "./common/user"; import { Context } from "./Context"; import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint"; @@ -27,6 +28,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { u.roles.push({ value: "admin" }); } } + const roles = await findOrCreateRole(ctx, u); const o = await ctx.rc.user.update({ userId: ctx.id(), data: { @@ -35,7 +37,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { username: u.userName, active: u.active, verified: true, - roles: u.roles.map((x) => x.value), + roles, customFields: { scimExternalId: u.externalId, }, diff --git a/src/endpoints/UsersEndpoint.ts b/src/endpoints/UsersEndpoint.ts index ba17cbf..3bf90dc 100644 --- a/src/endpoints/UsersEndpoint.ts +++ b/src/endpoints/UsersEndpoint.ts @@ -3,6 +3,7 @@ import { IApiResponse } from "@rocket.chat/apps-engine/definition/api"; import crypto = require("crypto"); import { SCIMListResponse } from "../scim/ListResponse"; import { SCIMUser } from "../scim/User"; +import { findOrCreateRole } from "./common/user"; import { Context } from "./Context"; import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint"; @@ -28,12 +29,13 @@ export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint { public async _post(ctx: Context): Promise { const u = SCIMUser.fromPlain(ctx.content()); + const roles = await findOrCreateRole(ctx, u); const o = await ctx.rc.user.create({ email: u.getEmail(), name: u.displayName || u.userName, username: u.userName, password: crypto.randomBytes(64).toString("base64").slice(0, 64), - roles: u.roles.map((x) => x.value), + roles, verified: true, customFields: { scimExternalId: u.externalId, diff --git a/src/endpoints/common/endpoint.ts b/src/endpoints/common/endpoint.ts new file mode 100644 index 0000000..2823515 --- /dev/null +++ b/src/endpoints/common/endpoint.ts @@ -0,0 +1,14 @@ +import { ConflictError } from "../../errors/ConflictError"; + +export function handleRcError(o: any) { + if (!o.success) { + if (o.error?.includes("already in use")) { + throw new ConflictError( + o.error.includes("@") ? "email" : "username", + ); + } + if (o.error?.includes("not found")) { + } + throw new Error(o.error); + } +} diff --git a/src/endpoints/common/user.ts b/src/endpoints/common/user.ts new file mode 100644 index 0000000..ba39d26 --- /dev/null +++ b/src/endpoints/common/user.ts @@ -0,0 +1,25 @@ +import { SCIMUser } from "../../scim/User"; +import { Context } from "../Context"; +import { handleRcError } from "./endpoint"; + +export async function findOrCreateRole( + ctx: Context, + user: SCIMUser, +): Promise> { + const listResp = await ctx.rc.role.list(); + handleRcError(listResp); + const rcRoles = listResp.roles; + const roles = await Promise.all( + user.roles.map(async ({ value }) => { + let rcRole = rcRoles.find((x) => x.name === value); + if (!rcRole) { + const r = await ctx.rc.role.create({ name: value }); + handleRcError(r); + rcRoles.push(r.role); + rcRole = r.role; + } + return rcRole._id; + }), + ); + return roles; +} diff --git a/src/rc-sdk/RcSdk.ts b/src/rc-sdk/RcSdk.ts index dc9493e..b3e8d5e 100644 --- a/src/rc-sdk/RcSdk.ts +++ b/src/rc-sdk/RcSdk.ts @@ -7,6 +7,7 @@ import { } from "@rocket.chat/apps-engine/definition/accessors"; import { EmptyResponseError } from "../errors/EmptyResponseError"; import { JsonParseError } from "../errors/JsonParseError"; +import { RcSdkRole } from "./RcSdkRole"; import { RcSdkTeam } from "./RcSdkTeam"; import { RcSdkUser } from "./RcSdkUser"; @@ -18,6 +19,7 @@ type RequestCallback = ( export class RcSdk { public user: RcSdkUser; public team: RcSdkTeam; + public role: RcSdkRole; private readonly baseUrl = "http://localhost:3000/api/v1"; private readonly http: IHttp; private readonly read: IRead; @@ -29,10 +31,11 @@ export class RcSdk { this.logger = logger; this.user = new RcSdkUser(this); this.team = new RcSdkTeam(this); + this.role = new RcSdkRole(this); } - public get(url: string, content?: any): Promise { - return this.request(this.http.get, url, content); + public get(url: string, content?: any): Promise { + return this.request(this.http.get, url, content); } public post(url: string, content?: any): Promise { diff --git a/src/rc-sdk/RcSdkRole.ts b/src/rc-sdk/RcSdkRole.ts new file mode 100644 index 0000000..cd70612 --- /dev/null +++ b/src/rc-sdk/RcSdkRole.ts @@ -0,0 +1,44 @@ +import { RcSdk } from "./RcSdk"; + +interface IRoleCreate { + name: string; + scope?: string; + description?: boolean; +} + +export interface IRole { + _id: string; + name: string; + scope?: string; + description?: boolean; + createdAt: string; + _updatedAt?: string; +} + +interface IRoleListResponse { + roles: Array; + count: number; + success: boolean; + error?: string; +} + +interface IRoleResponse { + role: IRole; + success: boolean; + error?: string; +} + +export class RcSdkRole { + private sdk: RcSdk; + constructor(sdk: RcSdk) { + this.sdk = sdk; + } + + public list(): Promise { + return this.sdk.get(`roles.list`); + } + + public create(body: IRoleCreate): Promise { + return this.sdk.post(`roles.create`, body); + } +}