diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 56c500ab..748db623 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -71,7 +71,7 @@ const containerService = new ContainerService(cryptoService, platformUtilsServic const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, false); const configurationService = new ConfigurationService(storageService, secureStorageService); -const syncSevrice = new SyncService(configurationService); +const syncSevrice = new SyncService(configurationService, logService); const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService); containerService.attachToWindow(window); diff --git a/src/services/azure-directory.service.ts b/src/services/azure-directory.service.ts index 35138238..01e33b85 100644 --- a/src/services/azure-directory.service.ts +++ b/src/services/azure-directory.service.ts @@ -5,7 +5,9 @@ import * as querystring from 'querystring'; import { DirectoryType } from '../enums/directoryType'; import { AzureConfiguration } from '../models/azureConfiguration'; +import { GroupEntry } from '../models/groupEntry'; import { SyncConfiguration } from '../models/syncConfiguration'; +import { UserEntry } from '../models/userEntry'; import { ConfigurationService } from './configuration.service'; import { DirectoryService } from './directory.service'; @@ -55,7 +57,7 @@ export class AzureDirectoryService implements DirectoryService { }); } - async getEntries(force = false) { + async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> { const type = await this.configurationService.getDirectoryType(); if (type !== DirectoryType.AzureActiveDirectory) { return; @@ -74,6 +76,8 @@ export class AzureDirectoryService implements DirectoryService { await this.getUsers(); await this.getGroups(); + + return null; } private async getUsers() { diff --git a/src/services/directory.service.ts b/src/services/directory.service.ts index 5f9cbc42..72bcaf4b 100644 --- a/src/services/directory.service.ts +++ b/src/services/directory.service.ts @@ -1,3 +1,6 @@ +import { GroupEntry } from '../models/groupEntry'; +import { UserEntry } from '../models/userEntry'; + export interface DirectoryService { - getEntries(force?: boolean): any; + getEntries(force?: boolean): Promise<[GroupEntry[], UserEntry[]]>; } diff --git a/src/services/gsuite-directory.service.ts b/src/services/gsuite-directory.service.ts index 7a3744ea..3bac0ef0 100644 --- a/src/services/gsuite-directory.service.ts +++ b/src/services/gsuite-directory.service.ts @@ -1,15 +1,26 @@ import { JWT } from 'google-auth-library'; -import { google, GoogleApis } from 'googleapis'; -import { Admin } from 'googleapis/build/src/apis/admin/directory_v1'; +import { + google, + GoogleApis, +} from 'googleapis'; +import { + Admin, + Schema$Group, + Schema$User, +} from 'googleapis/build/src/apis/admin/directory_v1'; import { DirectoryType } from '../enums/directoryType'; +import { GroupEntry } from '../models/groupEntry'; import { GSuiteConfiguration } from '../models/gsuiteConfiguration'; import { SyncConfiguration } from '../models/syncConfiguration'; +import { UserEntry } from '../models/userEntry'; import { ConfigurationService } from './configuration.service'; import { DirectoryService } from './directory.service'; +import { LogService } from 'jslib/abstractions/log.service'; + export class GSuiteDirectoryService implements DirectoryService { private client: JWT; private service: Admin; @@ -17,11 +28,11 @@ export class GSuiteDirectoryService implements DirectoryService { private dirConfig: GSuiteConfiguration; private syncConfig: SyncConfiguration; - constructor(private configurationService: ConfigurationService) { + constructor(private configurationService: ConfigurationService, private logService: LogService) { this.service = google.admin('directory_v1'); } - async getEntries(force = false) { + async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> { const type = await this.configurationService.getDirectoryType(); if (type !== DirectoryType.GSuite) { return; @@ -38,30 +49,201 @@ export class GSuiteDirectoryService implements DirectoryService { } await this.auth(); - await this.getUsers(); - await this.getGroups(); - } - private async getUsers() { - const response = await this.service.users.list(this.authParams); - console.log(response); - } - - private async getGroups() { - const response = await this.service.groups.list(this.authParams); - console.log(response); - - if (response.data.groups.length === 0) { - return; + let users: UserEntry[]; + if (this.syncConfig.users) { + users = await this.getUsers(); } - response.data.groups.forEach(async (g) => { - const params: any = Object.assign({ - groupKey: g.id, - }, this.authParams); - const members = await this.service.members.list(params); - console.log(members); + let groups: GroupEntry[]; + if (this.syncConfig.groups) { + groups = await this.getGroups(); + } + + return [groups, users]; + } + + private async getUsers(): Promise { + const entries: UserEntry[] = []; + const query = this.createQuery(this.syncConfig.userFilter); + + this.logService.info('Querying users.'); + let p = Object.assign({ query: query }, this.authParams); + const res = await this.service.users.list(p); + if (res.status !== 200) { + throw new Error('User list API failed: ' + res.statusText); + } + + const filter = this.createSet(this.syncConfig.userFilter); + if (res.data.users != null) { + res.data.users.forEach((user) => { + if (this.filterOutResult(filter, user.primaryEmail)) { + return; + } + + const entry = this.buildUser(user, false); + if (entry != null) { + entries.push(entry); + } + }); + } + + this.logService.info('Querying deleted users.'); + p = Object.assign({ showDeleted: true, query: query }, this.authParams); + const delRes = await this.service.users.list(p); + if (delRes.status !== 200) { + throw new Error('Deleted user list API failed: ' + delRes.statusText); + } + + if (delRes.data.users != null) { + delRes.data.users.forEach((user) => { + if (this.filterOutResult(filter, user.primaryEmail)) { + return; + } + + const entry = this.buildUser(user, true); + if (entry != null) { + entries.push(entry); + } + }); + } + + return entries; + } + + private buildUser(user: Schema$User, deleted: boolean) { + if ((user.emails == null || user.emails === '') && !deleted) { + return null; + } + + const entry = new UserEntry(); + entry.referenceId = user.id; + entry.externalId = user.id; + entry.email = user.primaryEmail; + entry.disabled = user.suspended || false; + entry.deleted = deleted; + // entry.creationDate = user.creationTime; // TODO: string to date conversion + return entry; + } + + private async getGroups(): Promise { + const entries: GroupEntry[] = []; + + this.logService.info('Querying groups.'); + const res = await this.service.groups.list(this.authParams); + if (res.status !== 200) { + throw new Error('Group list API failed: ' + res.statusText); + } + + const filter = this.createSet(this.syncConfig.groupFilter); + if (res.data.groups != null) { + res.data.groups.forEach(async (group) => { + if (this.filterOutResult(filter, group.name)) { + return; + } + + const entry = await this.buildGroup(group); + entries.push(entry); + }); + } + + return entries; + } + + private async buildGroup(group: Schema$Group) { + const entry = new GroupEntry(); + entry.referenceId = group.id; + entry.externalId = group.id; + entry.name = group.name; + + const p = Object.assign({ groupKey: group.id }, this.authParams); + const memRes = await this.service.members.list(p); + if (memRes.status !== 200) { + this.logService.warning('Group member list API failed: ' + memRes.statusText); + return entry; + } + + if (memRes.data.members != null) { + memRes.data.members.forEach((member) => { + if (member.role.toLowerCase() !== 'member') { + return; + } + if (member.status.toLowerCase() !== 'active') { + return; + } + + if (member.type.toLowerCase() === 'user') { + entry.userMemberExternalIds.add(member.id); + } else if (member.type.toLowerCase() === 'group') { + entry.groupMemberReferenceIds.add(member.id); + } + }); + } + + return entry; + } + + private createQuery(filter: string) { + if (filter == null || filter === '') { + return null; + } + + const mainParts = filter.split('|'); + if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === '') { + return null; + } + + return mainParts[1].trim(); + } + + private createSet(filter: string): [boolean, Set] { + if (filter == null || filter === '') { + return null; + } + + const mainParts = filter.split('|'); + if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') { + return null; + } + + const parts = mainParts[0].split(':'); + if (parts.length !== 2) { + return null; + } + + const keyword = parts[0].trim().toLowerCase(); + let exclude = true; + if (keyword === 'include') { + exclude = false; + } else if (keyword === 'exclude') { + exclude = true; + } else { + return null; + } + + const set = new Set(); + const pieces = parts[1].split(','); + pieces.forEach((p) => { + set.add(p.trim().toLowerCase()); }); + + return [exclude, set]; + } + + private filterOutResult(filter: [boolean, Set], result: string) { + if (filter != null) { + result = result.trim().toLowerCase(); + const excluded = filter[0]; + const set = filter[1]; + + if (excluded && set.has(result)) { + return true; + } else if (!excluded && !set.has(result)) { + return true; + } + } + + return false; } private async auth() { diff --git a/src/services/ldap-directory.service.ts b/src/services/ldap-directory.service.ts index 620dd8a8..ba332f02 100644 --- a/src/services/ldap-directory.service.ts +++ b/src/services/ldap-directory.service.ts @@ -2,8 +2,10 @@ import * as ldap from 'ldapjs'; import { DirectoryType } from '../enums/directoryType'; +import { GroupEntry } from '../models/groupEntry'; import { LdapConfiguration } from '../models/ldapConfiguration'; import { SyncConfiguration } from '../models/syncConfiguration'; +import { UserEntry } from '../models/userEntry'; import { ConfigurationService } from './configuration.service'; import { DirectoryService } from './directory.service'; @@ -15,7 +17,7 @@ export class LdapDirectoryService implements DirectoryService { constructor(private configurationService: ConfigurationService) { } - async getEntries(force = false) { + async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> { const type = await this.configurationService.getDirectoryType(); if (type !== DirectoryType.Ldap) { return; @@ -33,6 +35,8 @@ export class LdapDirectoryService implements DirectoryService { await this.auth(); await this.getUsers(); + + return null; } private getUsers() { diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts index 78884a2d..6fa95849 100644 --- a/src/services/sync.service.ts +++ b/src/services/sync.service.ts @@ -1,5 +1,6 @@ import { DirectoryType } from '../enums/directoryType'; +import { LogService } from 'jslib/abstractions/log.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { AzureDirectoryService } from './azure-directory.service'; @@ -14,7 +15,7 @@ const Keys = { export class SyncService { private dirType: DirectoryType; - constructor(private configurationService: ConfigurationService) { } + constructor(private configurationService: ConfigurationService, private logService: LogService) { } async sync(force = true, sendToServer = true): Promise { this.dirType = await this.configurationService.getDirectoryType(); @@ -27,13 +28,14 @@ export class SyncService { return; } - directoryService.getEntries(force); + const entries = await directoryService.getEntries(force); + console.log(entries); } private getDirectoryService(): DirectoryService { switch (this.dirType) { case DirectoryType.GSuite: - return new GSuiteDirectoryService(this.configurationService); + return new GSuiteDirectoryService(this.configurationService, this.logService); case DirectoryType.AzureActiveDirectory: return new AzureDirectoryService(this.configurationService); case DirectoryType.Ldap: