diff --git a/src/app/tabs/dashboard.component.ts b/src/app/tabs/dashboard.component.ts index 2b5e7509..1036d6b2 100644 --- a/src/app/tabs/dashboard.component.ts +++ b/src/app/tabs/dashboard.component.ts @@ -12,18 +12,16 @@ import { I18nService } from 'jslib/abstractions/i18n.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { StateService } from 'jslib/abstractions/state.service'; -import { AzureDirectoryService } from '../../services/azure-directory.service'; -import { GSuiteDirectoryService } from '../../services/gsuite-directory.service'; -import { LdapDirectoryService } from '../../services/ldap-directory.service'; import { SyncService } from '../../services/sync.service'; -import { Entry } from '../../models/entry'; import { GroupEntry } from '../../models/groupEntry'; import { UserEntry } from '../../models/userEntry'; import { ConfigurationService } from '../../services/configuration.service'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; +import { ConnectorUtils } from '../../utils'; + const BroadcasterSubscriptionId = 'DashboardComponent'; @Component({ @@ -105,63 +103,22 @@ export class DashboardComponent implements OnInit, OnDestroy { this.simPromise = new Promise(async (resolve, reject) => { try { - const result = await this.syncService.sync(!this.simSinceLast, true); - if (result[0] != null) { - this.simGroups = result[0]; - } - if (result[1] != null) { - this.simUsers = result[1]; - } + const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, this.simSinceLast); + this.simGroups = result.groups; + this.simUsers = result.users; + this.simEnabledUsers = result.enabledUsers; + this.simDisabledUsers = result.disabledUsers; + this.simDeletedUsers = result.deletedUsers; } catch (e) { this.simGroups = null; this.simUsers = null; - reject(e || this.i18nService.t('syncError')); + reject(e); return; } - - const userMap = new Map(); - this.sort(this.simUsers); - for (const u of this.simUsers) { - userMap.set(u.externalId, u); - if (u.deleted) { - this.simDeletedUsers.push(u); - } else if (u.disabled) { - this.simDisabledUsers.push(u); - } else { - this.simEnabledUsers.push(u); - } - } - - this.sort(this.simGroups); - for (const g of this.simGroups) { - if (g.userMemberExternalIds == null) { - continue; - } - - const anyG = (g as any); - anyG.users = []; - for (const uid of g.userMemberExternalIds) { - if (userMap.has(uid)) { - anyG.users.push(userMap.get(uid)); - } else { - anyG.users.push({ displayName: uid }); - } - } - - this.sort(anyG.users); - } - resolve(); }); } - private sort(arr: Entry[]) { - arr.sort((a, b) => { - return this.i18nService.collator ? this.i18nService.collator.compare(a.displayName, b.displayName) : - a.displayName.localeCompare(b.displayName); - }); - } - private async updateLastSync() { this.lastGroupSync = await this.configurationService.getLastGroupSyncDate(); this.lastUserSync = await this.configurationService.getLastUserSyncDate(); diff --git a/src/commands/test.command.ts b/src/commands/test.command.ts new file mode 100644 index 00000000..fa42c73b --- /dev/null +++ b/src/commands/test.command.ts @@ -0,0 +1,24 @@ +import * as program from 'commander'; + +import { I18nService } from 'jslib/abstractions/i18n.service'; + +import { SyncService } from '../services/sync.service'; + +import { ConnectorUtils } from '../utils'; + +import { Response } from 'jslib/cli/models/response'; +import { TestResponse } from '../models/response/testResponse'; + +export class TestCommand { + constructor(private syncService: SyncService, private i18nService: I18nService) { } + + async run(cmd: program.Command): Promise { + try { + const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false); + const res = new TestResponse(result); + return Response.success(res); + } catch (e) { + return Response.error(e); + } + } +} diff --git a/src/models/response/groupResponse.ts b/src/models/response/groupResponse.ts new file mode 100644 index 00000000..d0f9c4e3 --- /dev/null +++ b/src/models/response/groupResponse.ts @@ -0,0 +1,13 @@ +import { GroupEntry } from '../groupEntry'; + +export class GroupResponse { + externalId: string; + displayName: string; + userIds: string[]; + + constructor(g: GroupEntry) { + this.externalId = g.externalId; + this.displayName = g.displayName; + this.userIds = Array.from(g.userMemberExternalIds); + } +} diff --git a/src/models/response/testResponse.ts b/src/models/response/testResponse.ts new file mode 100644 index 00000000..49456438 --- /dev/null +++ b/src/models/response/testResponse.ts @@ -0,0 +1,22 @@ +import { GroupResponse } from './groupResponse'; +import { UserResponse } from './userResponse'; + +import { SimResult } from '../simResult'; + +import { BaseResponse } from 'jslib/cli/models/response/baseResponse'; + +export class TestResponse implements BaseResponse { + object: string; + groups: GroupResponse[] = []; + enabledUsers: UserResponse[] = []; + disabledUsers: UserResponse[] = []; + deletedUsers: UserResponse[] = []; + + constructor(result: SimResult) { + this.object = 'test'; + this.groups = result.groups != null ? result.groups.map((g) => new GroupResponse(g)) : []; + this.enabledUsers = result.enabledUsers != null ? result.enabledUsers.map((u) => new UserResponse(u)) : []; + this.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map((u) => new UserResponse(u)) : []; + this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map((u) => new UserResponse(u)) : []; + } +} diff --git a/src/models/response/userResponse.ts b/src/models/response/userResponse.ts new file mode 100644 index 00000000..aff5406c --- /dev/null +++ b/src/models/response/userResponse.ts @@ -0,0 +1,11 @@ +import { UserEntry } from '../userEntry'; + +export class UserResponse { + externalId: string; + displayName: string; + + constructor(u: UserEntry) { + this.externalId = u.externalId; + this.displayName = u.displayName; + } +} diff --git a/src/models/simResult.ts b/src/models/simResult.ts new file mode 100644 index 00000000..4adf301f --- /dev/null +++ b/src/models/simResult.ts @@ -0,0 +1,10 @@ +import { GroupEntry } from './groupEntry'; +import { UserEntry } from './userEntry'; + +export class SimResult { + groups: GroupEntry[] = []; + users: UserEntry[] = []; + enabledUsers: UserEntry[] = []; + disabledUsers: UserEntry[] = []; + deletedUsers: UserEntry[] = []; +} diff --git a/src/program.ts b/src/program.ts index 4e90c995..a44ad6cc 100644 --- a/src/program.ts +++ b/src/program.ts @@ -4,6 +4,7 @@ import * as program from 'commander'; import { Main } from './bwdc'; import { ConfigCommand } from './commands/config.command'; +import { TestCommand } from './commands/test.command'; import { UpdateCommand } from 'jslib/cli/commands/update.command'; @@ -57,10 +58,30 @@ export class Program { program.on('--help', () => { writeLn('\n Examples:'); writeLn(''); - writeLn(' bwdc login'); + writeLn(' bwdc test'); + writeLn(' bwdc config server bitwarden.com'); + writeLn(' bwdc update'); writeLn('', true); }); + program + .command('test') + .description('Test a simulated sync.') + .option('-l, --last', 'Since the last successful sync.') + .on('--help', () => { + writeLn('\n Examples:'); + writeLn(''); + writeLn(' bwdc test'); + writeLn(' bwdc test --last'); + writeLn('', true); + }) + .action(async (cmd) => { + await this.exitIfNotAuthed(); + const command = new TestCommand(this.main.syncService, this.main.i18nService); + const response = await command.run(cmd); + this.processResponse(response); + }); + program .command('config ') .description('Configure CLI settings.') @@ -187,14 +208,6 @@ export class Program { return out.trim() === '' ? null : out; } - private async exitIfLocked() { - await this.exitIfNotAuthed(); - const hasKey = await this.main.cryptoService.hasKey(); - if (!hasKey) { - this.processResponse(Response.error('Vault is locked.'), true); - } - } - private async exitIfAuthed() { const authed = await this.main.userService.isAuthenticated(); if (authed) { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..2fddf12c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,70 @@ +import { I18nService } from 'jslib/abstractions/i18n.service'; + +import { SyncService } from './services/sync.service'; + +import { Entry } from './models/entry'; +import { SimResult } from './models/simResult'; +import { UserEntry } from './models/userEntry'; + +export class ConnectorUtils { + static async simulate(syncService: SyncService, i18nService: I18nService, sinceLast: boolean): Promise { + return new Promise(async (resolve, reject) => { + const simResult = new SimResult(); + try { + const result = await syncService.sync(!sinceLast, true); + if (result[0] != null) { + simResult.groups = result[0]; + } + if (result[1] != null) { + simResult.users = result[1]; + } + } catch (e) { + simResult.groups = null; + simResult.users = null; + reject(e || i18nService.t('syncError')); + return; + } + + const userMap = new Map(); + this.sortEntries(simResult.users, i18nService); + for (const u of simResult.users) { + userMap.set(u.externalId, u); + if (u.deleted) { + simResult.deletedUsers.push(u); + } else if (u.disabled) { + simResult.disabledUsers.push(u); + } else { + simResult.enabledUsers.push(u); + } + } + + this.sortEntries(simResult.groups, i18nService); + for (const g of simResult.groups) { + if (g.userMemberExternalIds == null) { + continue; + } + + const anyG = (g as any); + anyG.users = []; + for (const uid of g.userMemberExternalIds) { + if (userMap.has(uid)) { + anyG.users.push(userMap.get(uid)); + } else { + anyG.users.push({ displayName: uid }); + } + } + + this.sortEntries(anyG.users, i18nService); + } + + resolve(simResult); + }); + } + + private static sortEntries(arr: Entry[], i18nService: I18nService) { + arr.sort((a, b) => { + return i18nService.collator ? i18nService.collator.compare(a.displayName, b.displayName) : + a.displayName.localeCompare(b.displayName); + }); + } +}