From 12d18d9e127577b3787fbba3b0d6e0bf31d3536b Mon Sep 17 00:00:00 2001 From: Vuthik Kol Date: Wed, 29 Nov 2023 10:33:56 +1100 Subject: [PATCH 1/2] IE-19/list-all-users + add all flag to list users --- src/scripts/list-users.test.ts | 16 ++++++---- src/scripts/list-users.ts | 46 ++++++++++++++++++---------- src/scripts/services/user-service.ts | 23 +++++++++----- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/scripts/list-users.test.ts b/src/scripts/list-users.test.ts index 1fc78a71..3e52d54e 100644 --- a/src/scripts/list-users.test.ts +++ b/src/scripts/list-users.test.ts @@ -21,7 +21,7 @@ describe('Listing all users', () => { }; // When we attempt to list the users from the client - const result = await users(userService)(); + const result = await users(userService, false)(); // Then we should have a right expect(result).toBeRight(); @@ -44,7 +44,7 @@ describe('Listing all users', () => { }; // When we attempt to list the users from the client - const result = await users(userService)(); + const result = await users(userService, false)(); // Then we should have a left expect(result).toEqualLeft(new Error('expected error')); @@ -71,7 +71,8 @@ describe('Listing users within groups', () => { const result = await usersInGroup( userService, groupService, - test.group.id + test.group.id, + false )(); // Then we should have a right @@ -108,7 +109,8 @@ describe('Listing users within groups', () => { const result = await usersInGroup( userService, groupService, - test.group.id + test.group.id, + false )(); // Then we should have a left @@ -137,7 +139,8 @@ describe('Listing users within groups', () => { const result = await usersInGroup( userService, groupService, - test.group.id + test.group.id, + false )(); // Then we should have a left @@ -166,7 +169,8 @@ describe('Listing users within groups', () => { const result = await usersInGroup( userService, groupService, - test.group.id + test.group.id, + false )(); // Then we should have a left diff --git a/src/scripts/list-users.ts b/src/scripts/list-users.ts index 5ed0befe..3184318f 100644 --- a/src/scripts/list-users.ts +++ b/src/scripts/list-users.ts @@ -4,6 +4,7 @@ import { RootCommand } from '..'; import { table } from 'table'; import { OktaUserService, User, UserService } from './services/user-service'; import { oktaReadOnlyClient } from './services/client-service'; +import * as okta from '@okta/okta-sdk-nodejs'; import * as TE from 'fp-ts/lib/TaskEither'; import * as E from 'fp-ts/lib/Either'; @@ -41,9 +42,9 @@ const usersTable = (users: readonly User[]): string => { ); }; -export const users = (service: UserService) => +export const users = (service: UserService, listAll: boolean) => pipe( - service.listUsers(), + service.listUsers(listAll), TE.map((users) => usersTable(users)), TE.chainFirstIOK(Console.info) ); @@ -51,7 +52,8 @@ export const users = (service: UserService) => export const usersInGroup = ( userService: UserService, groupService: GroupService, - groupId: string + groupId: string, + listAll: boolean ) => pipe( groupService.getGroup(groupId), @@ -62,7 +64,7 @@ export const usersInGroup = ( (group: Group) => TE.right(group) ) ), - TE.chain(userService.listUsersInGroup), + TE.chain((group) => userService.listUsersInGroup(group, listAll)), TE.map((users) => usersTable(users)), TE.chainFirstIOK(Console.info) ); @@ -74,40 +76,50 @@ export default ( readonly clientId: string; readonly privateKey: string; readonly organisationUrl: string; + readonly all: boolean; readonly groupId?: string; }> => rootCommand.command( 'list-users', - // eslint-disable-next-line quotes - "Provides a list of all users' ID's, email addresses, display names, and statuses. Allows a specification of a group to list from.", + `Provides a list of all users' ID's, email addresses, display names, and statuses, except for ${okta.UserStatus.DEPROVISIONED} users. Allows a specification of a group to list from.`, // eslint-disable-next-line functional/no-return-void, @typescript-eslint/prefer-readonly-parameter-types (yargs) => { // eslint-disable-next-line functional/no-expression-statement - yargs.positional('group', { - type: 'string', - alias: ['group-id'], - // eslint-disable-next-line quotes - describe: "The group's ID", - }); + yargs + .positional('group', { + type: 'string', + alias: ['group-id'], + // eslint-disable-next-line quotes + describe: "The group's ID", + }) + .option('all', { + alias: 'all', + type: 'boolean', + describe: `if true, will list all users, including ${okta.UserStatus.DEPROVISIONED} ones`, + demandOption: false, + default: false, + }); }, async (args: { readonly clientId: string; readonly privateKey: string; readonly organisationUrl: string; + readonly all: boolean; readonly groupId?: string; }) => { + const { groupId, all } = args; const client = oktaReadOnlyClient( { ...args }, - args.groupId === undefined ? ['users'] : ['groups'] + groupId === undefined ? ['users'] : ['groups'] ); const userService = new OktaUserService(client); const groupService = new OktaGroupService(client); // eslint-disable-next-line functional/no-expression-statement - Console.info(args.groupId); - const result: E.Either = await (args.groupId === undefined - ? users(userService) - : usersInGroup(userService, groupService, args.groupId))(); + Console.info(groupId); + const result: E.Either = await (groupId === undefined + ? users(userService, all) + : usersInGroup(userService, groupService, groupId, all))(); // eslint-disable-next-line functional/no-conditional-statement if (E.isLeft(result)) { diff --git a/src/scripts/services/user-service.ts b/src/scripts/services/user-service.ts index 82710b5d..a54071b5 100644 --- a/src/scripts/services/user-service.ts +++ b/src/scripts/services/user-service.ts @@ -109,13 +109,15 @@ export class OktaUserService { ); }; - // eslint-disable-next-line functional/functional-parameters - readonly listUsers = (): TE.TaskEither => + readonly listUsers = ( + listAll: boolean + ): TE.TaskEither => // eslint-disable-next-line functional/no-this-expression - this.privateListUsers(TE.right(this.client)); + this.privateListUsers(TE.right(this.client), listAll); readonly listUsersInGroup = ( - group: Group + group: Group, + listAll: boolean ): TE.TaskEither => pipe( group, @@ -129,11 +131,12 @@ export class OktaUserService { }) ), // eslint-disable-next-line functional/no-this-expression - this.privateListUsers + (group) => this.privateListUsers(group, listAll) ); readonly privateListUsers = ( - groupOrClient: TE.TaskEither + groupOrClient: TE.TaskEither, + listAll: boolean ) => // We need to populate users with all of the client data so it can be // returned. Okta's listUsers() function returns a custom collection that @@ -146,7 +149,13 @@ export class OktaUserService { const users: User[] = []; return ( maybeGroupOrClient - .listUsers() + .listUsers({ + filter: listAll + ? Object.keys(okta.UserStatus) + .map((status) => `status eq "${status}"`) + .join(' or ') + : undefined, + }) // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types .each((oktaUser) => { // eslint-disable-next-line functional/immutable-data From 7a96d0311eb5ea9e65b7a6114332f7c01b957ae4 Mon Sep 17 00:00:00 2001 From: Vuthik Kol Date: Wed, 29 Nov 2023 14:22:24 +1100 Subject: [PATCH 2/2] IE-19/list-all-users = added link for list users documentation --- src/scripts/services/user-service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/scripts/services/user-service.ts b/src/scripts/services/user-service.ts index a54071b5..a91045d5 100644 --- a/src/scripts/services/user-service.ts +++ b/src/scripts/services/user-service.ts @@ -134,6 +134,13 @@ export class OktaUserService { (group) => this.privateListUsers(group, listAll) ); + /** + * Lists all users in a group or client. + * @param groupOrClient - Either a group or a client + * @param listAl - Whether to list all users or just non-deprovisioned users + * @link https://developer.okta.com/docs/reference/api/users/#list-all-users + * @returns a list of users + */ readonly privateListUsers = ( groupOrClient: TE.TaskEither, listAll: boolean @@ -150,6 +157,8 @@ export class OktaUserService { return ( maybeGroupOrClient .listUsers({ + // Without this filter, Okta will only return non-deprovisioned users. + // link: https://developer.okta.com/docs/reference/api/users/#list-all-users filter: listAll ? Object.keys(okta.UserStatus) .map((status) => `status eq "${status}"`)