import { HttpStatusCode, IHttp, IHttpResponse, IModify, IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse, } from "@rocket.chat/apps-engine/definition/api"; import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; import { ConflictError } from "../errors/ConflictError"; 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 = ( request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence, ) => Promise; export interface IScimEndpoint { _get?(ctx: Context): Promise; _post?(ctx: Context): Promise; _put?(ctx: Context): Promise; _delete?(ctx: Context): Promise; } export abstract class ScimEndpoint extends ApiEndpoint { public get: ApiEndpointMethod | undefined; public post: ApiEndpointMethod | undefined; public put: ApiEndpointMethod | undefined; public delete: ApiEndpointMethod | undefined; protected handleError = handleRcError; constructor(app: IApp) { super(app); this.get = this.wrapMethod("get"); this.post = this.wrapMethod("post"); this.put = this.wrapMethod("put"); this.delete = this.wrapMethod("delete"); } protected success(content?: any): IApiResponse { return this.response({ status: HttpStatusCode.OK, content, }); } protected response(response: IApiResponse): IApiResponse { if (response.headers === undefined) { response.headers = {}; } response.headers["Content-Type"] = "application/scim+json"; return response; } protected error(error: SCIMError): IApiResponse { return this.response({ status: parseInt(error.status, 10), content: error, }); } protected parseResponse(response: IHttpResponse): any { if (!response.content) { throw new EmptyResponseError(); } let content: any; try { content = JSON.parse(response.content); } catch (e) { throw new JsonParseError(); } return content; } protected hasContent(request: IApiRequest) { if (!request.content || Object.keys(request.content).length === 0) { throw new EmptyRequestError(); } } private wrapMethod(name: string): ApiEndpointMethod | undefined { const method = this[`_${name}`]; if (method === undefined || typeof method !== "function") { return undefined; } return async ( request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence, ): Promise => { const logger = this.app.getLogger(); try { const ctx = new Context( request, endpoint, read, modify, http, persis, logger, ); logger.debug( `SCIM Request ${name.toUpperCase()} /${this.path}`, request.content, ); const response = await method.bind(this)(ctx); logger.debug("SCIM Response", response); return response; } catch (e) { let err: SCIMError; if (e.toSCIMError && typeof e.toSCIMError === "function") { err = e.toSCIMError(); } else { err = new SCIMError() .setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR) .setScimType(SCIMErrorType.INVALID_VALUE) .setDetail(e.message); } logger.error(e); return this.error(err); } }; } }