scim errors
This commit is contained in:
parent
9cc58730dd
commit
835c2aa53c
11 changed files with 398 additions and 220 deletions
3
.prettierrc
Normal file
3
.prettierrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
76
RcHttp.ts
Normal file
76
RcHttp.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import {
|
||||||
|
IHttp,
|
||||||
|
IHttpRequest,
|
||||||
|
IHttpResponse,
|
||||||
|
IRead,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
|
||||||
|
export class RcHttp implements IHttp {
|
||||||
|
private readonly baseUrl = "http://localhost:3000/api/v1";
|
||||||
|
private readonly http: IHttp;
|
||||||
|
private readonly read: IRead;
|
||||||
|
|
||||||
|
constructor(http: IHttp, read: IRead) {
|
||||||
|
this.http = http;
|
||||||
|
this.read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
|
return this.http.get(
|
||||||
|
this.buildUrl(url),
|
||||||
|
await this.buildOptions(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async post(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
|
return this.http.post(
|
||||||
|
this.buildUrl(url),
|
||||||
|
await this.buildOptions(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async put(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
|
return this.http.put(
|
||||||
|
this.buildUrl(url),
|
||||||
|
await this.buildOptions(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async del(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
|
return this.http.del(
|
||||||
|
this.buildUrl(url),
|
||||||
|
await this.buildOptions(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async patch(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
|
return this.http.patch(
|
||||||
|
this.buildUrl(url),
|
||||||
|
await this.buildOptions(content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildUrl(url: string): string {
|
||||||
|
return `${this.baseUrl}/${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildOptions(content?: any): Promise<IHttpRequest> {
|
||||||
|
const options: IHttpRequest = {
|
||||||
|
headers: {
|
||||||
|
"X-User-Id": await this.read
|
||||||
|
.getEnvironmentReader()
|
||||||
|
.getSettings()
|
||||||
|
.getValueById("rc-user-id"),
|
||||||
|
"X-Auth-Token": await this.read
|
||||||
|
.getEnvironmentReader()
|
||||||
|
.getSettings()
|
||||||
|
.getValueById("rc-token"),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (content !== undefined) {
|
||||||
|
options.content = JSON.stringify(content);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
171
ScimEndpoint.ts
Normal file
171
ScimEndpoint.ts
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
type ApiEndpointMethod = (
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
) => Promise<IApiResponse>;
|
||||||
|
|
||||||
|
export interface IScimEndpoint {
|
||||||
|
_get?(
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
): Promise<IApiResponse>;
|
||||||
|
_post?(
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
): Promise<IApiResponse>;
|
||||||
|
_put?(
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
): Promise<IApiResponse>;
|
||||||
|
_delete?(
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
): Promise<IApiResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ScimEndpoint extends ApiEndpoint {
|
||||||
|
public get: ApiEndpointMethod | undefined;
|
||||||
|
public post: ApiEndpointMethod | undefined;
|
||||||
|
public put: ApiEndpointMethod | undefined;
|
||||||
|
public delete: ApiEndpointMethod | undefined;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return async (
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
): Promise<IApiResponse> => {
|
||||||
|
try {
|
||||||
|
return await method.bind(this)(
|
||||||
|
request,
|
||||||
|
endpoint,
|
||||||
|
read,
|
||||||
|
modify,
|
||||||
|
http,
|
||||||
|
persis,
|
||||||
|
);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
155
UserEndpoint.ts
155
UserEndpoint.ts
|
@ -6,161 +6,74 @@ import {
|
||||||
IRead,
|
IRead,
|
||||||
} from "@rocket.chat/apps-engine/definition/accessors";
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
import {
|
import {
|
||||||
ApiEndpoint,
|
|
||||||
IApiEndpointInfo,
|
IApiEndpointInfo,
|
||||||
IApiRequest,
|
IApiRequest,
|
||||||
IApiResponse,
|
IApiResponse,
|
||||||
} from "@rocket.chat/apps-engine/definition/api";
|
} from "@rocket.chat/apps-engine/definition/api";
|
||||||
|
import { RcHttp } from "./RcHttp";
|
||||||
import { SCIMUser } from "./scim/User";
|
import { SCIMUser } from "./scim/User";
|
||||||
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
|
||||||
export class UserEndpoint extends ApiEndpoint {
|
export class UserEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
public path = "Users/:id";
|
public path = "Users/:id";
|
||||||
|
|
||||||
public async get(
|
public async _get(
|
||||||
request: IApiRequest,
|
request: IApiRequest,
|
||||||
endpoint: IApiEndpointInfo,
|
endpoint: IApiEndpointInfo,
|
||||||
read: IRead,
|
read: IRead,
|
||||||
modify: IModify,
|
modify: IModify,
|
||||||
http: IHttp,
|
http: IHttp,
|
||||||
persis: IPersistence
|
persis: IPersistence,
|
||||||
): Promise<IApiResponse> {
|
): Promise<IApiResponse> {
|
||||||
let user: SCIMUser;
|
const response = await new RcHttp(http, read).get(
|
||||||
try {
|
`users.info?userId=${request.params.id}`,
|
||||||
const response = await http.get(
|
);
|
||||||
`http://localhost:3000/api/v1/users.info?userId=` +
|
const o = this.parseResponse(response);
|
||||||
request.params.id,
|
this.handleError(o);
|
||||||
{
|
const user = SCIMUser.fromRC(o.user);
|
||||||
headers: {
|
return this.success(user);
|
||||||
...(await this.getAuthHeaders(read)),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.content) throw new Error("Empty response");
|
|
||||||
const o = JSON.parse(response.content);
|
|
||||||
if (!o.success) throw new Error(o.error);
|
|
||||||
user = SCIMUser.fromRC(o.user);
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
content: { message: e.message },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.OK,
|
|
||||||
content: user,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async put(
|
public async _put(
|
||||||
request: IApiRequest,
|
request: IApiRequest,
|
||||||
endpoint: IApiEndpointInfo,
|
endpoint: IApiEndpointInfo,
|
||||||
read: IRead,
|
read: IRead,
|
||||||
modify: IModify,
|
modify: IModify,
|
||||||
http: IHttp,
|
http: IHttp,
|
||||||
persis: IPersistence
|
persis: IPersistence,
|
||||||
): Promise<IApiResponse> {
|
): Promise<IApiResponse> {
|
||||||
let user: SCIMUser;
|
this.hasContent(request);
|
||||||
try {
|
const response = await new RcHttp(http, read).post(
|
||||||
const response = await http.post(
|
"users.update",
|
||||||
"http://localhost:3000/api/v1/users.update",
|
this.scimToUserUpdate(
|
||||||
{
|
request.params.id,
|
||||||
headers: {
|
SCIMUser.fromPlain(request.content),
|
||||||
...(await this.getAuthHeaders(read)),
|
),
|
||||||
"Content-Type": "application/json",
|
);
|
||||||
},
|
const o = this.parseResponse(response);
|
||||||
content: JSON.stringify(
|
this.handleError(o);
|
||||||
this.scimToUserUpdate(
|
const user = SCIMUser.fromRC(o.user);
|
||||||
request.params.id,
|
return this.success(user);
|
||||||
SCIMUser.fromPlain(request.content)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.content) throw new Error("Empty response");
|
|
||||||
const o = JSON.parse(response.content);
|
|
||||||
if (!o.success) throw new Error(o.error);
|
|
||||||
user = SCIMUser.fromRC(o.user);
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
content: { message: e.message },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.OK,
|
|
||||||
content: user,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(
|
public async _delete(
|
||||||
request: IApiRequest,
|
request: IApiRequest,
|
||||||
endpoint: IApiEndpointInfo,
|
endpoint: IApiEndpointInfo,
|
||||||
read: IRead,
|
read: IRead,
|
||||||
modify: IModify,
|
modify: IModify,
|
||||||
http: IHttp,
|
http: IHttp,
|
||||||
persis: IPersistence
|
persis: IPersistence,
|
||||||
): Promise<IApiResponse> {
|
): Promise<IApiResponse> {
|
||||||
let d: IUserDelete = {
|
const d: IUserDelete = {
|
||||||
userId: request.params.id,
|
userId: request.params.id,
|
||||||
confirmRelinquish: true,
|
confirmRelinquish: true,
|
||||||
};
|
};
|
||||||
try {
|
const response = await new RcHttp(http, read).post("users.delete", d);
|
||||||
const response = await http.post(
|
const o = this.parseResponse(response);
|
||||||
"http://localhost:3000/api/v1/users.delete",
|
this.handleError(o);
|
||||||
{
|
return this.response({
|
||||||
headers: {
|
|
||||||
...(await this.getAuthHeaders(read)),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
content: JSON.stringify(d),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.content) throw new Error("Empty response");
|
|
||||||
const o = JSON.parse(response.content);
|
|
||||||
if (!o.success) throw new Error(o.error);
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
content: { message: e.message },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.NO_CONTENT,
|
status: HttpStatusCode.NO_CONTENT,
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private async getAuthHeaders(
|
|
||||||
read: IRead
|
|
||||||
): Promise<{ [key: string]: string }> {
|
|
||||||
return {
|
|
||||||
"X-User-Id": await read
|
|
||||||
.getEnvironmentReader()
|
|
||||||
.getSettings()
|
|
||||||
.getValueById("rc-user-id"),
|
|
||||||
"X-Auth-Token": await read
|
|
||||||
.getEnvironmentReader()
|
|
||||||
.getSettings()
|
|
||||||
.getValueById("rc-token"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private scimToUserUpdate(userId: string, user: SCIMUser): IUserUpdate {
|
private scimToUserUpdate(userId: string, user: SCIMUser): IUserUpdate {
|
||||||
|
|
121
UsersEndpoint.ts
121
UsersEndpoint.ts
|
@ -6,128 +6,67 @@ import {
|
||||||
IRead,
|
IRead,
|
||||||
} from "@rocket.chat/apps-engine/definition/accessors";
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
import {
|
import {
|
||||||
ApiEndpoint,
|
|
||||||
IApiEndpointInfo,
|
IApiEndpointInfo,
|
||||||
IApiRequest,
|
IApiRequest,
|
||||||
IApiResponse,
|
IApiResponse,
|
||||||
} from "@rocket.chat/apps-engine/definition/api";
|
} from "@rocket.chat/apps-engine/definition/api";
|
||||||
import crypto = require("crypto");
|
import crypto = require("crypto");
|
||||||
import { SCIMError, SCIMErrorType } from "./scim/Error";
|
import { RcHttp } from "./RcHttp";
|
||||||
import { SCIMListResponse } from "./scim/ListResponse";
|
import { SCIMListResponse } from "./scim/ListResponse";
|
||||||
import { SCIMUser } from "./scim/User";
|
import { SCIMUser } from "./scim/User";
|
||||||
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
|
||||||
export class UsersEndpoint extends ApiEndpoint {
|
export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
public path = "Users";
|
public path = "Users";
|
||||||
|
|
||||||
public async get(
|
public async _get(
|
||||||
request: IApiRequest,
|
request: IApiRequest,
|
||||||
endpoint: IApiEndpointInfo,
|
endpoint: IApiEndpointInfo,
|
||||||
read: IRead,
|
read: IRead,
|
||||||
modify: IModify,
|
modify: IModify,
|
||||||
http: IHttp,
|
http: IHttp,
|
||||||
persis: IPersistence
|
persis: IPersistence,
|
||||||
): Promise<IApiResponse> {
|
): Promise<IApiResponse> {
|
||||||
|
const response = await new RcHttp(http, read).get(
|
||||||
|
`users.list?query={"type":{"$eq":"user"}}&fields={"createdAt":1}`,
|
||||||
|
);
|
||||||
|
const o = this.parseResponse(response);
|
||||||
|
this.handleError(o);
|
||||||
const list = new SCIMListResponse();
|
const list = new SCIMListResponse();
|
||||||
try {
|
list.Resources = o.users.map(SCIMUser.fromRC);
|
||||||
const response = await http.get(
|
list.totalResults = o.total;
|
||||||
`http://localhost:3000/api/v1/users.list?query={"type":{"$eq":"user"}}&fields={"createdAt":1}`,
|
return this.success(list);
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
...(await this.getAuthHeaders(read)),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.content) {
|
|
||||||
throw new Error("Empty response");
|
|
||||||
}
|
|
||||||
const o = JSON.parse(response.content);
|
|
||||||
if (!o.success) {
|
|
||||||
throw new Error(o.error);
|
|
||||||
}
|
|
||||||
list.Resources = o.users.map(SCIMUser.fromRC);
|
|
||||||
list.totalResults = o.total;
|
|
||||||
} catch (e) {
|
|
||||||
const err = new SCIMError();
|
|
||||||
err.scimType = SCIMErrorType.INVALID_VALUE;
|
|
||||||
err.detail = e.message;
|
|
||||||
err.status = "400";
|
|
||||||
return err.toApiResponse();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.OK,
|
|
||||||
content: list,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async post(
|
public async _post(
|
||||||
request: IApiRequest,
|
request: IApiRequest,
|
||||||
endpoint: IApiEndpointInfo,
|
endpoint: IApiEndpointInfo,
|
||||||
read: IRead,
|
read: IRead,
|
||||||
modify: IModify,
|
modify: IModify,
|
||||||
http: IHttp,
|
http: IHttp,
|
||||||
persis: IPersistence
|
persis: IPersistence,
|
||||||
): Promise<IApiResponse> {
|
): Promise<IApiResponse> {
|
||||||
let user = request.content;
|
this.hasContent(request);
|
||||||
try {
|
const response = await new RcHttp(http, read).post(
|
||||||
const response = await http.post(
|
`users.create`,
|
||||||
`http://localhost:3000/api/v1/users.create`,
|
this.scimToUserCreate(SCIMUser.fromPlain(request.content)),
|
||||||
{
|
);
|
||||||
headers: {
|
const o = this.parseResponse(response);
|
||||||
...(await this.getAuthHeaders(read)),
|
this.handleError(o);
|
||||||
"Content-Type": "application/json",
|
const user = SCIMUser.fromRC(o.user);
|
||||||
},
|
return this.response({
|
||||||
content: JSON.stringify(
|
|
||||||
this.scimToUserCreate(SCIMUser.fromPlain(user))
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.content) {
|
|
||||||
throw new Error("Empty response");
|
|
||||||
}
|
|
||||||
const o = JSON.parse(response.content);
|
|
||||||
if (!o.success) {
|
|
||||||
throw new Error(o.error);
|
|
||||||
}
|
|
||||||
user = SCIMUser.fromRC(o.user);
|
|
||||||
} catch (e) {
|
|
||||||
const err = new SCIMError();
|
|
||||||
err.scimType = SCIMErrorType.INVALID_VALUE;
|
|
||||||
err.detail = e.message;
|
|
||||||
err.status = "400";
|
|
||||||
return err.toApiResponse();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/scim+json",
|
|
||||||
},
|
|
||||||
status: HttpStatusCode.CREATED,
|
status: HttpStatusCode.CREATED,
|
||||||
content: user,
|
content: user,
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private async getAuthHeaders(
|
|
||||||
read: IRead
|
|
||||||
): Promise<{ [key: string]: string }> {
|
|
||||||
return {
|
|
||||||
"X-User-Id": await read
|
|
||||||
.getEnvironmentReader()
|
|
||||||
.getSettings()
|
|
||||||
.getValueById("rc-user-id"),
|
|
||||||
"X-Auth-Token": await read
|
|
||||||
.getEnvironmentReader()
|
|
||||||
.getSettings()
|
|
||||||
.getValueById("rc-token"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private scimToUserCreate(user: SCIMUser): IUserCreate {
|
private scimToUserCreate(user: SCIMUser): IUserCreate {
|
||||||
return {
|
return {
|
||||||
email: user.emails[0].value,
|
email: user.getEmail(),
|
||||||
name: user.displayName,
|
name:
|
||||||
|
user.displayName ||
|
||||||
|
`${user.name.givenName} ${user.name.familyName}` ||
|
||||||
|
user.userName,
|
||||||
username: user.userName,
|
username: user.userName,
|
||||||
password: crypto.randomBytes(64).toString("base64").slice(0, 64),
|
password: crypto.randomBytes(64).toString("base64").slice(0, 64),
|
||||||
verified: true,
|
verified: true,
|
||||||
|
|
6
errors/BaseError.ts
Normal file
6
errors/BaseError.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { SCIMError } from "../scim/Error";
|
||||||
|
|
||||||
|
export abstract class BaseError extends Error {
|
||||||
|
public readonly isBaseError = true;
|
||||||
|
public abstract toSCIMError(): SCIMError;
|
||||||
|
}
|
22
errors/ConflictError.ts
Normal file
22
errors/ConflictError.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { SCIMError, SCIMErrorType } from "../scim/Error";
|
||||||
|
import { BaseError } from "./BaseError";
|
||||||
|
|
||||||
|
export class ConflictError extends BaseError {
|
||||||
|
public get message() {
|
||||||
|
return `This ${this.type} already exists`;
|
||||||
|
}
|
||||||
|
private type = "";
|
||||||
|
|
||||||
|
constructor(type: string) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toSCIMError(): SCIMError {
|
||||||
|
return new SCIMError()
|
||||||
|
.setStatus(HttpStatusCode.CONFLICT)
|
||||||
|
.setScimType(SCIMErrorType.INVALID_VALUE)
|
||||||
|
.setDetail(this.message);
|
||||||
|
}
|
||||||
|
}
|
13
errors/EmptyRequestError.ts
Normal file
13
errors/EmptyRequestError.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { SCIMError, SCIMErrorType } from "../scim/Error";
|
||||||
|
import { BaseError } from "./BaseError";
|
||||||
|
|
||||||
|
export class EmptyRequestError extends BaseError {
|
||||||
|
public message = "Request body is empty or content type is unsupported";
|
||||||
|
public toSCIMError(): SCIMError {
|
||||||
|
return new SCIMError()
|
||||||
|
.setStatus(HttpStatusCode.BAD_REQUEST)
|
||||||
|
.setScimType(SCIMErrorType.INVALID_VALUE)
|
||||||
|
.setDetail(this.message);
|
||||||
|
}
|
||||||
|
}
|
13
errors/EmptyResponseError.ts
Normal file
13
errors/EmptyResponseError.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { SCIMError, SCIMErrorType } from "../scim/Error";
|
||||||
|
import { BaseError } from "./BaseError";
|
||||||
|
|
||||||
|
export class EmptyResponseError extends BaseError {
|
||||||
|
public message = "Rocket.Chat API returned an empty response";
|
||||||
|
public toSCIMError(): SCIMError {
|
||||||
|
return new SCIMError()
|
||||||
|
.setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR)
|
||||||
|
.setScimType(SCIMErrorType.INVALID_VALUE)
|
||||||
|
.setDetail(this.message);
|
||||||
|
}
|
||||||
|
}
|
13
errors/JsonParseError.ts
Normal file
13
errors/JsonParseError.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { SCIMError, SCIMErrorType } from "../scim/Error";
|
||||||
|
import { BaseError } from "./BaseError";
|
||||||
|
|
||||||
|
export class JsonParseError extends BaseError {
|
||||||
|
public message = "Failed to parse Rocket.Chat API response content";
|
||||||
|
public toSCIMError(): SCIMError {
|
||||||
|
return new SCIMError()
|
||||||
|
.setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR)
|
||||||
|
.setScimType(SCIMErrorType.INVALID_VALUE)
|
||||||
|
.setDetail(this.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { IApiResponse } from "@rocket.chat/apps-engine/definition/api";
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
|
||||||
export enum SCIMErrorType {
|
export enum SCIMErrorType {
|
||||||
INVALID_FILTER = "invalidFilter",
|
INVALID_FILTER = "invalidFilter",
|
||||||
|
@ -27,15 +27,24 @@ export enum SCIMErrorDetail {
|
||||||
NOT_IMPLEMENTED = "Service provider does not support the request operation.",
|
NOT_IMPLEMENTED = "Service provider does not support the request operation.",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SCIMError {
|
export class SCIMError {
|
||||||
public readonly schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"];
|
public readonly schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"];
|
||||||
public scimType: SCIMErrorType;
|
public scimType: SCIMErrorType;
|
||||||
public detail: SCIMErrorDetail | string;
|
public detail: SCIMErrorDetail | string;
|
||||||
public status: string;
|
public status: string;
|
||||||
public toApiResponse(): IApiResponse {
|
|
||||||
return {
|
public setScimType(scimType: SCIMErrorType): SCIMError {
|
||||||
status: parseInt(this.status, 10),
|
this.scimType = scimType;
|
||||||
content: this,
|
return this;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public setStatus(status: HttpStatusCode): SCIMError {
|
||||||
|
this.status = `${status}`;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDetail(detail: SCIMErrorDetail | string): SCIMError {
|
||||||
|
this.detail = detail;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue