From 5f8b88aa0bdd63b7a1ebd012cabc1934edac0426 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Tue, 26 Jul 2022 11:39:55 +0000 Subject: [PATCH] Grouping access endpoing (#1858) * Grouping access endpoing * Add username --- src/lib/openapi/index.ts | 2 + src/lib/openapi/spec/group-schema.ts | 4 +- .../spec/users-groups-base-schema.test.ts | 15 +++++ .../openapi/spec/users-groups-base-schema.ts | 33 ++++++++++ src/lib/routes/admin-api/user-admin.ts | 65 ++++++++++++++++++- src/lib/types/group.ts | 3 +- src/lib/types/stores/group-store.ts | 2 +- .../__snapshots__/openapi.e2e.test.ts.snap | 41 +++++++++++- 8 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/lib/openapi/spec/users-groups-base-schema.test.ts create mode 100644 src/lib/openapi/spec/users-groups-base-schema.ts diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 96088158b0..35bfa20b48 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -104,6 +104,7 @@ import { URL } from 'url'; import { groupSchema } from './spec/group-schema'; import { groupsSchema } from './spec/groups-schema'; import { groupUserModelSchema } from './spec/group-user-model-schema'; +import { usersGroupsBaseSchema } from './spec/users-groups-base-schema'; // All schemas in `openapi/spec` should be listed here. export const schemas = { @@ -198,6 +199,7 @@ export const schemas = { updateUserSchema, upsertContextFieldSchema, upsertStrategySchema, + usersGroupsBaseSchema, userSchema, usersSchema, usersSearchSchema, diff --git a/src/lib/openapi/spec/group-schema.ts b/src/lib/openapi/spec/group-schema.ts index 96464383a1..de209b1083 100644 --- a/src/lib/openapi/spec/group-schema.ts +++ b/src/lib/openapi/spec/group-schema.ts @@ -5,8 +5,8 @@ import { userSchema } from './user-schema'; export const groupSchema = { $id: '#/components/schemas/groupSchema', type: 'object', - additionalProperties: false, - required: ['name', 'users'], + additionalProperties: true, + required: ['name'], properties: { id: { type: 'number', diff --git a/src/lib/openapi/spec/users-groups-base-schema.test.ts b/src/lib/openapi/spec/users-groups-base-schema.test.ts new file mode 100644 index 0000000000..1a0ee6d46d --- /dev/null +++ b/src/lib/openapi/spec/users-groups-base-schema.test.ts @@ -0,0 +1,15 @@ +import { validateSchema } from '../validate'; +import { UsersGroupsBaseSchema } from './users-groups-base-schema'; + +test('usersGroupsBaseSchema', () => { + const data: UsersGroupsBaseSchema = { + users: [ + { + id: 1, + }, + ], + }; + expect( + validateSchema('#/components/schemas/usersGroupsBaseSchema', data), + ).toBeUndefined(); +}); diff --git a/src/lib/openapi/spec/users-groups-base-schema.ts b/src/lib/openapi/spec/users-groups-base-schema.ts new file mode 100644 index 0000000000..1cae4f6a28 --- /dev/null +++ b/src/lib/openapi/spec/users-groups-base-schema.ts @@ -0,0 +1,33 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { groupSchema } from './group-schema'; +import { userSchema } from './user-schema'; +import { groupUserModelSchema } from './group-user-model-schema'; + +export const usersGroupsBaseSchema = { + $id: '#/components/schemas/usersGroupsBaseSchema', + type: 'object', + additionalProperties: false, + properties: { + groups: { + type: 'array', + items: { + $ref: '#/components/schemas/groupSchema', + }, + }, + users: { + type: 'array', + items: { + $ref: '#/components/schemas/userSchema', + }, + }, + }, + components: { + schemas: { + groupSchema, + groupUserModelSchema, + userSchema, + }, + }, +} as const; + +export type UsersGroupsBaseSchema = FromSchema; diff --git a/src/lib/routes/admin-api/user-admin.ts b/src/lib/routes/admin-api/user-admin.ts index 8283c29552..d13a44cac7 100644 --- a/src/lib/routes/admin-api/user-admin.ts +++ b/src/lib/routes/admin-api/user-admin.ts @@ -4,10 +4,9 @@ import { ADMIN, NONE } from '../../types/permissions'; import UserService from '../../services/user-service'; import { AccessService } from '../../services/access-service'; import { Logger } from '../../logger'; -import { IUnleashConfig } from '../../types/option'; +import { IUnleashConfig, IUnleashServices } from '../../types'; import { EmailService } from '../../services/email-service'; import ResetTokenService from '../../services/reset-token-service'; -import { IUnleashServices } from '../../types/services'; import { IAuthRequest } from '../unleash-types'; import SettingService from '../../services/setting-service'; import { IUser, SimpleAuthSettings } from '../../server-impl'; @@ -32,6 +31,12 @@ import { ResetPasswordSchema, } from '../../openapi/spec/reset-password-schema'; import { emptyResponse } from '../../openapi/util/standard-responses'; +import { GroupService } from '../../services/group-service'; +import { + UsersGroupsBaseSchema, + usersGroupsBaseSchema, +} from '../../openapi/spec/users-groups-base-schema'; +import { IGroup } from '../../types/group'; export default class UserAdminController extends Controller { private anonymise: boolean = false; @@ -50,6 +55,8 @@ export default class UserAdminController extends Controller { private openApiService: OpenApiService; + private groupService: GroupService; + readonly unleashUrl: string; constructor( @@ -61,6 +68,7 @@ export default class UserAdminController extends Controller { resetTokenService, settingService, openApiService, + groupService, }: Pick< IUnleashServices, | 'userService' @@ -69,6 +77,7 @@ export default class UserAdminController extends Controller { | 'resetTokenService' | 'settingService' | 'openApiService' + | 'groupService' >, ) { super(config); @@ -78,6 +87,7 @@ export default class UserAdminController extends Controller { this.resetTokenService = resetTokenService; this.settingService = settingService; this.openApiService = openApiService; + this.groupService = groupService; this.logger = config.getLogger('routes/user-controller.ts'); this.unleashUrl = config.server.unleashUrl; this.anonymise = config.experimental?.anonymiseEventLog; @@ -147,7 +157,7 @@ export default class UserAdminController extends Controller { method: 'get', path: '/search', handler: this.searchUsers, - permission: ADMIN, + permission: NONE, middleware: [ openApiService.validPath({ tags: ['admin'], @@ -157,6 +167,22 @@ export default class UserAdminController extends Controller { ], }); + this.route({ + method: 'get', + path: '/access', + handler: this.getBaseUsersAndGroups, + permission: NONE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getBaseUsersAndGroups', + responses: { + 200: createResponseSchema('usersGroupsBaseSchema'), + }, + }), + ], + }); + this.route({ method: 'post', path: '', @@ -279,6 +305,39 @@ export default class UserAdminController extends Controller { ); } + async getBaseUsersAndGroups( + req: Request, + res: Response, + ): Promise { + let allUsers = await this.userService.getAll(); + let users = allUsers.map((u) => { + return { + id: u.id, + name: u.name, + username: u.username, + email: u.email, + } as IUser; + }); + + let allGroups = await this.groupService.getAll(); + let groups = allGroups.map((g) => { + return { + id: g.id, + name: g.name, + userCount: g.users.length, + } as IGroup; + }); + this.openApiService.respondWithValidation( + 200, + res, + usersGroupsBaseSchema.$id, + { + users: serializeDates(users), + groups: serializeDates(groups), + }, + ); + } + async getUser(req: Request, res: Response): Promise { const { id } = req.params; const user = await this.userService.getUser(Number(id)); diff --git a/src/lib/types/group.ts b/src/lib/types/group.ts index fd2503a094..811a07e8b9 100644 --- a/src/lib/types/group.ts +++ b/src/lib/types/group.ts @@ -4,8 +4,9 @@ import { IUser } from './user'; export interface IGroup { id?: number; name: string; - description: string; + description?: string; createdAt?: Date; + userCount?: number; createdBy?: string; } diff --git a/src/lib/types/stores/group-store.ts b/src/lib/types/stores/group-store.ts index 7cddce3fe6..7569a20203 100644 --- a/src/lib/types/stores/group-store.ts +++ b/src/lib/types/stores/group-store.ts @@ -10,7 +10,7 @@ import { export interface IStoreGroup { name: string; - description: string; + description?: string; } export interface IGroupStore extends Store { diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 4d37280550..54178a2264 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -1469,7 +1469,7 @@ Object { "type": "object", }, "groupSchema": Object { - "additionalProperties": false, + "additionalProperties": true, "properties": Object { "createdAt": Object { "format": "date-time", @@ -1504,7 +1504,6 @@ Object { }, "required": Array [ "name", - "users", ], "type": "object", }, @@ -2728,6 +2727,24 @@ Object { ], "type": "object", }, + "usersGroupsBaseSchema": Object { + "additionalProperties": false, + "properties": Object { + "groups": Object { + "items": Object { + "$ref": "#/components/schemas/groupSchema", + }, + "type": "array", + }, + "users": Object { + "items": Object { + "$ref": "#/components/schemas/userSchema", + }, + "type": "array", + }, + }, + "type": "object", + }, "usersSchema": Object { "additionalProperties": false, "properties": Object { @@ -5710,6 +5727,26 @@ If the provided project does not exist, the list of events will be empty.", ], }, }, + "/api/admin/user-admin/access": Object { + "get": Object { + "operationId": "getBaseUsersAndGroups", + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/usersGroupsBaseSchema", + }, + }, + }, + "description": "usersGroupsBaseSchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, "/api/admin/user-admin/reset-password": Object { "post": Object { "operationId": "resetUserPassword",