add group endpoints
This commit is contained in:
parent
835c2aa53c
commit
4f1fa62c83
14 changed files with 493 additions and 139 deletions
52
Context.ts
Normal file
52
Context.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
IHttp,
|
||||||
|
IModify,
|
||||||
|
IPersistence,
|
||||||
|
IRead,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import {
|
||||||
|
IApiEndpointInfo,
|
||||||
|
IApiRequest,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/api";
|
||||||
|
import { EmptyRequestError } from "./errors/EmptyRequestError";
|
||||||
|
import { RcSdk } from "./rc-sdk/RcSdk";
|
||||||
|
|
||||||
|
export class Context {
|
||||||
|
public readonly rc: RcSdk;
|
||||||
|
public readonly request: IApiRequest;
|
||||||
|
public readonly endpoint: IApiEndpointInfo;
|
||||||
|
public readonly read: IRead;
|
||||||
|
public readonly modify: IModify;
|
||||||
|
public readonly http: IHttp;
|
||||||
|
public readonly persis: IPersistence;
|
||||||
|
constructor(
|
||||||
|
request: IApiRequest,
|
||||||
|
endpoint: IApiEndpointInfo,
|
||||||
|
read: IRead,
|
||||||
|
modify: IModify,
|
||||||
|
http: IHttp,
|
||||||
|
persis: IPersistence,
|
||||||
|
) {
|
||||||
|
this.rc = new RcSdk(http, read);
|
||||||
|
this.request = request;
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.read = read;
|
||||||
|
this.modify = modify;
|
||||||
|
this.http = http;
|
||||||
|
this.persis = persis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public id(): string {
|
||||||
|
return this.request.params.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public content(): any {
|
||||||
|
if (
|
||||||
|
!this.request.content ||
|
||||||
|
Object.keys(this.request.content).length === 0
|
||||||
|
) {
|
||||||
|
throw new EmptyRequestError();
|
||||||
|
}
|
||||||
|
return this.request.content;
|
||||||
|
}
|
||||||
|
}
|
81
GroupEndpoint.ts
Normal file
81
GroupEndpoint.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { IApiResponse } from "@rocket.chat/apps-engine/definition/api";
|
||||||
|
import { Context } from "./Context";
|
||||||
|
import { SCIMGroup } from "./scim/Group";
|
||||||
|
import { SCIMUser } from "./scim/User";
|
||||||
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
|
||||||
|
export class GroupEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
|
public path = "Groups/:id";
|
||||||
|
|
||||||
|
public async _get(ctx: Context): Promise<IApiResponse> {
|
||||||
|
const getTeamInfo = async () => {
|
||||||
|
const teamRaw = await ctx.rc.team.info(ctx.id());
|
||||||
|
this.handleError(teamRaw);
|
||||||
|
return teamRaw.teamInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTeamMembers = async () => {
|
||||||
|
const membersRaw = await ctx.rc.team.members(ctx.id());
|
||||||
|
this.handleError(membersRaw);
|
||||||
|
return membersRaw.members;
|
||||||
|
};
|
||||||
|
|
||||||
|
const teamArgs = await Promise.all([getTeamInfo(), getTeamMembers()]);
|
||||||
|
const group = SCIMGroup.fromRC(...teamArgs);
|
||||||
|
return this.success(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async _put(ctx: Context): Promise<IApiResponse> {
|
||||||
|
const membersRaw = await ctx.rc.team.members(ctx.id());
|
||||||
|
this.handleError(membersRaw);
|
||||||
|
const targetIds = new Set<string>(
|
||||||
|
SCIMGroup.fromPlain(ctx.content()).members.map((x) => x.value),
|
||||||
|
);
|
||||||
|
const currentIds = new Set<string>(
|
||||||
|
membersRaw.members.map((x: ITeamMember) => x.user._id),
|
||||||
|
);
|
||||||
|
const removeMember = async (userId: string) => {
|
||||||
|
const removeMembersRaw = await ctx.rc.team.removeMember({
|
||||||
|
userId,
|
||||||
|
teamId: ctx.id(),
|
||||||
|
});
|
||||||
|
this.handleError(removeMembersRaw);
|
||||||
|
};
|
||||||
|
const addMembers = async (userIds: Array<string>) => {
|
||||||
|
const addMembersRaw = await ctx.rc.team.addMembers({
|
||||||
|
teamId: ctx.id(),
|
||||||
|
members: userIds.map((userId) => ({
|
||||||
|
userId,
|
||||||
|
roles: ["member"],
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
this.handleError(addMembersRaw);
|
||||||
|
};
|
||||||
|
const promises: Array<Promise<void>> = [];
|
||||||
|
for (const currentId of currentIds) {
|
||||||
|
if (!targetIds.has(currentId)) {
|
||||||
|
promises.push(removeMember(currentId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const addMemberIds: Array<string> = [];
|
||||||
|
for (const targetId of targetIds) {
|
||||||
|
if (!currentIds.has(targetId)) {
|
||||||
|
addMemberIds.push(targetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (addMemberIds.length > 0) {
|
||||||
|
promises.push(addMembers(addMemberIds));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
return this._get(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async _delete(ctx: Context): Promise<IApiResponse> {
|
||||||
|
const o = await ctx.rc.team.delete({ teamId: ctx.id() });
|
||||||
|
this.handleError(o);
|
||||||
|
return this.response({
|
||||||
|
status: HttpStatusCode.NO_CONTENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
53
GroupsEndpoint.ts
Normal file
53
GroupsEndpoint.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import {
|
||||||
|
HttpStatusCode,
|
||||||
|
IHttp,
|
||||||
|
IModify,
|
||||||
|
IPersistence,
|
||||||
|
IRead,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import {
|
||||||
|
IApiEndpointInfo,
|
||||||
|
IApiRequest,
|
||||||
|
IApiResponse,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/api";
|
||||||
|
import crypto = require("crypto");
|
||||||
|
import { Context } from "./Context";
|
||||||
|
import { RcHttp } from "./RcHttp";
|
||||||
|
import { SCIMGroup } from "./scim/Group";
|
||||||
|
import { SCIMListResponse } from "./scim/ListResponse";
|
||||||
|
import { SCIMUser } from "./scim/User";
|
||||||
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
|
||||||
|
export class GroupsEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
|
public path = "Groups";
|
||||||
|
|
||||||
|
public async _get(ctx: Context): Promise<IApiResponse> {
|
||||||
|
const teamsRaw = await ctx.rc.team.listAll();
|
||||||
|
this.handleError(teamsRaw);
|
||||||
|
const groups = teamsRaw.teams.map(async (team: ITeam) => {
|
||||||
|
const membersRaw = await ctx.rc.team.members(team._id);
|
||||||
|
this.handleError(membersRaw);
|
||||||
|
return SCIMGroup.fromRC(team, membersRaw.members);
|
||||||
|
});
|
||||||
|
const list = new SCIMListResponse();
|
||||||
|
list.Resources = await Promise.all(groups);
|
||||||
|
list.totalResults = teamsRaw.total;
|
||||||
|
return this.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async _post(ctx: Context): Promise<IApiResponse> {
|
||||||
|
const u = SCIMGroup.fromPlain(ctx.content());
|
||||||
|
const o = await ctx.rc.team.create({
|
||||||
|
name: u.displayName,
|
||||||
|
type: 0,
|
||||||
|
members: u.members.map((x) => x.value),
|
||||||
|
});
|
||||||
|
this.handleError(o);
|
||||||
|
const m = await ctx.rc.team.members(o.team._id);
|
||||||
|
const group = SCIMGroup.fromRC(o.team, m.members);
|
||||||
|
return this.response({
|
||||||
|
status: HttpStatusCode.CREATED,
|
||||||
|
content: group,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
10
RcHttp.ts
10
RcHttp.ts
|
@ -18,35 +18,35 @@ export class RcHttp implements IHttp {
|
||||||
public async get(url: string, content?: any): Promise<IHttpResponse> {
|
public async get(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
return this.http.get(
|
return this.http.get(
|
||||||
this.buildUrl(url),
|
this.buildUrl(url),
|
||||||
await this.buildOptions(content)
|
await this.buildOptions(content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async post(url: string, content?: any): Promise<IHttpResponse> {
|
public async post(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
return this.http.post(
|
return this.http.post(
|
||||||
this.buildUrl(url),
|
this.buildUrl(url),
|
||||||
await this.buildOptions(content)
|
await this.buildOptions(content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async put(url: string, content?: any): Promise<IHttpResponse> {
|
public async put(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
return this.http.put(
|
return this.http.put(
|
||||||
this.buildUrl(url),
|
this.buildUrl(url),
|
||||||
await this.buildOptions(content)
|
await this.buildOptions(content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async del(url: string, content?: any): Promise<IHttpResponse> {
|
public async del(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
return this.http.del(
|
return this.http.del(
|
||||||
this.buildUrl(url),
|
this.buildUrl(url),
|
||||||
await this.buildOptions(content)
|
await this.buildOptions(content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async patch(url: string, content?: any): Promise<IHttpResponse> {
|
public async patch(url: string, content?: any): Promise<IHttpResponse> {
|
||||||
return this.http.patch(
|
return this.http.patch(
|
||||||
this.buildUrl(url),
|
this.buildUrl(url),
|
||||||
await this.buildOptions(content)
|
await this.buildOptions(content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
import { App } from "@rocket.chat/apps-engine/definition/App";
|
import { App } from "@rocket.chat/apps-engine/definition/App";
|
||||||
import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata";
|
import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata";
|
||||||
import { SettingType } from "@rocket.chat/apps-engine/definition/settings";
|
import { SettingType } from "@rocket.chat/apps-engine/definition/settings";
|
||||||
|
import { GroupEndpoint } from "./GroupEndpoint";
|
||||||
|
import { GroupsEndpoint } from "./GroupsEndpoint";
|
||||||
|
|
||||||
import { UserEndpoint } from "./UserEndpoint";
|
import { UserEndpoint } from "./UserEndpoint";
|
||||||
import { UsersEndpoint } from "./UsersEndpoint";
|
import { UsersEndpoint } from "./UsersEndpoint";
|
||||||
|
@ -23,7 +25,12 @@ export class ScimApp extends App {
|
||||||
configuration.api.provideApi({
|
configuration.api.provideApi({
|
||||||
visibility: ApiVisibility.PUBLIC,
|
visibility: ApiVisibility.PUBLIC,
|
||||||
security: ApiSecurity.UNSECURE,
|
security: ApiSecurity.UNSECURE,
|
||||||
endpoints: [new UsersEndpoint(this), new UserEndpoint(this)],
|
endpoints: [
|
||||||
|
new UsersEndpoint(this),
|
||||||
|
new UserEndpoint(this),
|
||||||
|
new GroupsEndpoint(this),
|
||||||
|
new GroupEndpoint(this),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
configuration.settings.provideSetting({
|
configuration.settings.provideSetting({
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
IApiResponse,
|
IApiResponse,
|
||||||
} from "@rocket.chat/apps-engine/definition/api";
|
} from "@rocket.chat/apps-engine/definition/api";
|
||||||
import { IApp } from "@rocket.chat/apps-engine/definition/IApp";
|
import { IApp } from "@rocket.chat/apps-engine/definition/IApp";
|
||||||
|
import { Context } from "./Context";
|
||||||
import { ConflictError } from "./errors/ConflictError";
|
import { ConflictError } from "./errors/ConflictError";
|
||||||
import { EmptyRequestError } from "./errors/EmptyRequestError";
|
import { EmptyRequestError } from "./errors/EmptyRequestError";
|
||||||
import { EmptyResponseError } from "./errors/EmptyResponseError";
|
import { EmptyResponseError } from "./errors/EmptyResponseError";
|
||||||
|
@ -29,38 +30,10 @@ type ApiEndpointMethod = (
|
||||||
) => Promise<IApiResponse>;
|
) => Promise<IApiResponse>;
|
||||||
|
|
||||||
export interface IScimEndpoint {
|
export interface IScimEndpoint {
|
||||||
_get?(
|
_get?(ctx: Context): Promise<IApiResponse>;
|
||||||
request: IApiRequest,
|
_post?(ctx: Context): Promise<IApiResponse>;
|
||||||
endpoint: IApiEndpointInfo,
|
_put?(ctx: Context): Promise<IApiResponse>;
|
||||||
read: IRead,
|
_delete?(ctx: Context): Promise<IApiResponse>;
|
||||||
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 {
|
export abstract class ScimEndpoint extends ApiEndpoint {
|
||||||
|
@ -120,12 +93,12 @@ export abstract class ScimEndpoint extends ApiEndpoint {
|
||||||
|
|
||||||
protected handleError(o: any) {
|
protected handleError(o: any) {
|
||||||
if (!o.success) {
|
if (!o.success) {
|
||||||
if (o.error.includes("already in use")) {
|
if (o.error?.includes("already in use")) {
|
||||||
throw new ConflictError(
|
throw new ConflictError(
|
||||||
o.error.includes("@") ? "email" : "username",
|
o.error.includes("@") ? "email" : "username",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (o.error.includes("not found")) {
|
if (o.error?.includes("not found")) {
|
||||||
}
|
}
|
||||||
throw new Error(o.error);
|
throw new Error(o.error);
|
||||||
}
|
}
|
||||||
|
@ -146,12 +119,7 @@ export abstract class ScimEndpoint extends ApiEndpoint {
|
||||||
): Promise<IApiResponse> => {
|
): Promise<IApiResponse> => {
|
||||||
try {
|
try {
|
||||||
return await method.bind(this)(
|
return await method.bind(this)(
|
||||||
request,
|
new Context(request, endpoint, read, modify, http, persis),
|
||||||
endpoint,
|
|
||||||
read,
|
|
||||||
modify,
|
|
||||||
http,
|
|
||||||
persis,
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let err: SCIMError;
|
let err: SCIMError;
|
||||||
|
|
|
@ -1,75 +1,33 @@
|
||||||
import {
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
HttpStatusCode,
|
import { IApiResponse } from "@rocket.chat/apps-engine/definition/api";
|
||||||
IHttp,
|
import { Context } from "./Context";
|
||||||
IModify,
|
|
||||||
IPersistence,
|
|
||||||
IRead,
|
|
||||||
} from "@rocket.chat/apps-engine/definition/accessors";
|
|
||||||
import {
|
|
||||||
IApiEndpointInfo,
|
|
||||||
IApiRequest,
|
|
||||||
IApiResponse,
|
|
||||||
} 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";
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
|
||||||
export class UserEndpoint extends ScimEndpoint implements IScimEndpoint {
|
export class UserEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
public path = "Users/:id";
|
public path = "Users/:id";
|
||||||
|
|
||||||
public async _get(
|
public async _get(ctx: Context): Promise<IApiResponse> {
|
||||||
request: IApiRequest,
|
const o = await ctx.rc.user.info(ctx.id());
|
||||||
endpoint: IApiEndpointInfo,
|
|
||||||
read: IRead,
|
|
||||||
modify: IModify,
|
|
||||||
http: IHttp,
|
|
||||||
persis: IPersistence,
|
|
||||||
): Promise<IApiResponse> {
|
|
||||||
const response = await new RcHttp(http, read).get(
|
|
||||||
`users.info?userId=${request.params.id}`,
|
|
||||||
);
|
|
||||||
const o = this.parseResponse(response);
|
|
||||||
this.handleError(o);
|
this.handleError(o);
|
||||||
const user = SCIMUser.fromRC(o.user);
|
const user = SCIMUser.fromRC(o.user);
|
||||||
return this.success(user);
|
return this.success(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async _put(
|
public async _put(ctx: Context): Promise<IApiResponse> {
|
||||||
request: IApiRequest,
|
const o = await ctx.rc.user.update(
|
||||||
endpoint: IApiEndpointInfo,
|
this.scimToUserUpdate(ctx.id(), SCIMUser.fromPlain(ctx.content())),
|
||||||
read: IRead,
|
|
||||||
modify: IModify,
|
|
||||||
http: IHttp,
|
|
||||||
persis: IPersistence,
|
|
||||||
): Promise<IApiResponse> {
|
|
||||||
this.hasContent(request);
|
|
||||||
const response = await new RcHttp(http, read).post(
|
|
||||||
"users.update",
|
|
||||||
this.scimToUserUpdate(
|
|
||||||
request.params.id,
|
|
||||||
SCIMUser.fromPlain(request.content),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const o = this.parseResponse(response);
|
|
||||||
this.handleError(o);
|
this.handleError(o);
|
||||||
const user = SCIMUser.fromRC(o.user);
|
const user = SCIMUser.fromRC(o.user);
|
||||||
return this.success(user);
|
return this.success(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async _delete(
|
public async _delete(ctx: Context): Promise<IApiResponse> {
|
||||||
request: IApiRequest,
|
const o = await ctx.rc.user.delete({
|
||||||
endpoint: IApiEndpointInfo,
|
userId: ctx.id(),
|
||||||
read: IRead,
|
|
||||||
modify: IModify,
|
|
||||||
http: IHttp,
|
|
||||||
persis: IPersistence,
|
|
||||||
): Promise<IApiResponse> {
|
|
||||||
const d: IUserDelete = {
|
|
||||||
userId: request.params.id,
|
|
||||||
confirmRelinquish: true,
|
confirmRelinquish: true,
|
||||||
};
|
});
|
||||||
const response = await new RcHttp(http, read).post("users.delete", d);
|
|
||||||
const o = this.parseResponse(response);
|
|
||||||
this.handleError(o);
|
this.handleError(o);
|
||||||
return this.response({
|
return this.response({
|
||||||
status: HttpStatusCode.NO_CONTENT,
|
status: HttpStatusCode.NO_CONTENT,
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
import {
|
import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
HttpStatusCode,
|
import { IApiResponse } from "@rocket.chat/apps-engine/definition/api";
|
||||||
IHttp,
|
|
||||||
IModify,
|
|
||||||
IPersistence,
|
|
||||||
IRead,
|
|
||||||
} from "@rocket.chat/apps-engine/definition/accessors";
|
|
||||||
import {
|
|
||||||
IApiEndpointInfo,
|
|
||||||
IApiRequest,
|
|
||||||
IApiResponse,
|
|
||||||
} from "@rocket.chat/apps-engine/definition/api";
|
|
||||||
import crypto = require("crypto");
|
import crypto = require("crypto");
|
||||||
import { RcHttp } from "./RcHttp";
|
import { Context } from "./Context";
|
||||||
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";
|
import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
|
@ -19,18 +9,8 @@ import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint";
|
||||||
export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint {
|
export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
public path = "Users";
|
public path = "Users";
|
||||||
|
|
||||||
public async _get(
|
public async _get(ctx: Context): Promise<IApiResponse> {
|
||||||
request: IApiRequest,
|
const o = await ctx.rc.user.list();
|
||||||
endpoint: IApiEndpointInfo,
|
|
||||||
read: IRead,
|
|
||||||
modify: IModify,
|
|
||||||
http: IHttp,
|
|
||||||
persis: IPersistence,
|
|
||||||
): 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);
|
this.handleError(o);
|
||||||
const list = new SCIMListResponse();
|
const list = new SCIMListResponse();
|
||||||
list.Resources = o.users.map(SCIMUser.fromRC);
|
list.Resources = o.users.map(SCIMUser.fromRC);
|
||||||
|
@ -38,20 +18,10 @@ export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint {
|
||||||
return this.success(list);
|
return this.success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async _post(
|
public async _post(ctx: Context): Promise<IApiResponse> {
|
||||||
request: IApiRequest,
|
const o = await ctx.rc.user.create(
|
||||||
endpoint: IApiEndpointInfo,
|
this.scimToUserCreate(SCIMUser.fromPlain(ctx.content())),
|
||||||
read: IRead,
|
|
||||||
modify: IModify,
|
|
||||||
http: IHttp,
|
|
||||||
persis: IPersistence,
|
|
||||||
): Promise<IApiResponse> {
|
|
||||||
this.hasContent(request);
|
|
||||||
const response = await new RcHttp(http, read).post(
|
|
||||||
`users.create`,
|
|
||||||
this.scimToUserCreate(SCIMUser.fromPlain(request.content)),
|
|
||||||
);
|
);
|
||||||
const o = this.parseResponse(response);
|
|
||||||
this.handleError(o);
|
this.handleError(o);
|
||||||
const user = SCIMUser.fromRC(o.user);
|
const user = SCIMUser.fromRC(o.user);
|
||||||
return this.response({
|
return this.response({
|
||||||
|
|
96
rc-sdk/RcSdk.ts
Normal file
96
rc-sdk/RcSdk.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import {
|
||||||
|
IHttp,
|
||||||
|
IHttpRequest,
|
||||||
|
IHttpResponse,
|
||||||
|
IRead,
|
||||||
|
} from "@rocket.chat/apps-engine/definition/accessors";
|
||||||
|
import { EmptyResponseError } from "../errors/EmptyResponseError";
|
||||||
|
import { JsonParseError } from "../errors/JsonParseError";
|
||||||
|
import { RcSdkTeam } from "./RcSdkTeam";
|
||||||
|
import { RcSdkUser } from "./RcSdkUser";
|
||||||
|
|
||||||
|
export class RcSdk {
|
||||||
|
public user: RcSdkUser;
|
||||||
|
public team: RcSdkTeam;
|
||||||
|
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;
|
||||||
|
this.user = new RcSdkUser(this);
|
||||||
|
this.team = new RcSdkTeam(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
70
rc-sdk/RcSdkTeam.ts
Normal file
70
rc-sdk/RcSdkTeam.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { RcSdk } from "./RcSdk";
|
||||||
|
|
||||||
|
interface ITeamRemoveMemberBody {
|
||||||
|
teamId: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITeamAddMemberBody {
|
||||||
|
teamId: string;
|
||||||
|
members: Array<{
|
||||||
|
userId: string;
|
||||||
|
roles: Array<string>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITeamDeleteBody {
|
||||||
|
teamId: string;
|
||||||
|
roomsToRemove?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITeamCreateBody {
|
||||||
|
name: string;
|
||||||
|
type: 0 | 1;
|
||||||
|
members?: Array<string>;
|
||||||
|
room?: {
|
||||||
|
readOnly: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RcSdkTeam {
|
||||||
|
private sdk: RcSdk;
|
||||||
|
constructor(sdk: RcSdk) {
|
||||||
|
this.sdk = sdk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listAll(): Promise<any> {
|
||||||
|
const response = await this.sdk.get(`teams.listAll`);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async members(teamId: string): Promise<any> {
|
||||||
|
const response = await this.sdk.get(`teams.members?teamId=${teamId}`);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async info(teamId: string): Promise<any> {
|
||||||
|
const response = await this.sdk.get(`teams.info?teamId=${teamId}`);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(body: ITeamDeleteBody): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`teams.delete`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(body: ITeamCreateBody): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`teams.create`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeMember(body: ITeamRemoveMemberBody): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`teams.removeMember`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addMembers(body: ITeamAddMemberBody): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`teams.addMembers`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
}
|
35
rc-sdk/RcSdkUser.ts
Normal file
35
rc-sdk/RcSdkUser.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { RcSdk } from "./RcSdk";
|
||||||
|
|
||||||
|
export class RcSdkUser {
|
||||||
|
private sdk: RcSdk;
|
||||||
|
constructor(sdk: RcSdk) {
|
||||||
|
this.sdk = sdk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<any> {
|
||||||
|
const response = await this.sdk.get(
|
||||||
|
`users.list?query={"type":{"$eq":"user"}}&fields={"createdAt":1}`,
|
||||||
|
);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async info(userId: string): Promise<any> {
|
||||||
|
const response = await this.sdk.get(`users.info?userId=${userId}`);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(body: IUserUpdate): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`users.update`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(body: IUserDelete): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`users.delete`);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(body: IUserCreate): Promise<any> {
|
||||||
|
const response = await this.sdk.post(`users.create`, body);
|
||||||
|
return this.sdk.parseResponse(response);
|
||||||
|
}
|
||||||
|
}
|
18
rest.ts
18
rest.ts
|
@ -41,4 +41,22 @@ interface IUser {
|
||||||
username: string;
|
username: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
_updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITeam {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
active?: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
_updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITeamMember {
|
||||||
|
user: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
40
scim/Group.ts
Normal file
40
scim/Group.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { ISCIMGroupMember, ISCIMResource } from "./Interfaces";
|
||||||
|
import { SCIMMeta } from "./Meta";
|
||||||
|
|
||||||
|
export class SCIMGroup implements ISCIMResource {
|
||||||
|
public static fromPlain(plain: SCIMGroup): SCIMGroup {
|
||||||
|
const group = new SCIMGroup();
|
||||||
|
group.id = plain.id;
|
||||||
|
group.externalId = plain.externalId;
|
||||||
|
group.displayName = plain.displayName;
|
||||||
|
group.members = plain.members || [];
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromRC(rc: ITeam, members: Array<ITeamMember>): SCIMGroup {
|
||||||
|
const group = new SCIMGroup();
|
||||||
|
group.id = rc._id;
|
||||||
|
group.externalId = rc._id;
|
||||||
|
group.displayName = rc.name;
|
||||||
|
group.meta.created = new Date(rc.createdAt);
|
||||||
|
group.meta.lastModified = new Date(rc._updatedAt || rc.createdAt);
|
||||||
|
group.members = members.map((member) => ({
|
||||||
|
value: member.user._id,
|
||||||
|
$ref: `/Users/${member.user._id}`,
|
||||||
|
display: member.user.name,
|
||||||
|
type: "User",
|
||||||
|
}));
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly schemas = ["urn:ietf:params:scim:schemas:core:2.0:Group"];
|
||||||
|
public meta: SCIMMeta;
|
||||||
|
public id: string;
|
||||||
|
public externalId: string;
|
||||||
|
public displayName: string;
|
||||||
|
public members: Array<ISCIMGroupMember> = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.meta = new SCIMMeta("Group", () => this.id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,13 @@ export interface ISCIMListResponse {
|
||||||
{
|
{
|
||||||
id: "c75ad752-64ae-4823-840d-ffa80929976c";
|
id: "c75ad752-64ae-4823-840d-ffa80929976c";
|
||||||
userName: "jsmith";
|
userName: "jsmith";
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISCIMGroupMember {
|
||||||
|
value: string;
|
||||||
|
$ref: string;
|
||||||
|
display: string;
|
||||||
|
type?: "User" | "Group";
|
||||||
|
}
|
||||||
|
|
Reference in a new issue