diff --git a/src/app/tabs/dashboard.component.ts b/src/app/tabs/dashboard.component.ts index 030814a6..24b4cf87 100644 --- a/src/app/tabs/dashboard.component.ts +++ b/src/app/tabs/dashboard.component.ts @@ -15,7 +15,7 @@ export class DashboardComponent { constructor(private i18nService: I18nService, private syncService: SyncService) { } async sync() { - await this.syncService.sync(true, true); + await this.syncService.sync(false, true); } async simulate() { diff --git a/src/services/azure-directory.service.ts b/src/services/azure-directory.service.ts index 01e33b85..0ec98861 100644 --- a/src/services/azure-directory.service.ts +++ b/src/services/azure-directory.service.ts @@ -1,4 +1,5 @@ import * as graph from '@microsoft/microsoft-graph-client'; +import * as graphType from '@microsoft/microsoft-graph-types'; import * as https from 'https'; import * as querystring from 'querystring'; @@ -12,12 +13,168 @@ import { UserEntry } from '../models/userEntry'; import { ConfigurationService } from './configuration.service'; import { DirectoryService } from './directory.service'; +const NextLink = '@odata.nextLink'; +const DeltaLink = '@odata.deltaLink'; + export class AzureDirectoryService implements DirectoryService { private client: graph.Client; private dirConfig: AzureConfiguration; private syncConfig: SyncConfiguration; constructor(private configurationService: ConfigurationService) { + this.init(); + } + + async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> { + const type = await this.configurationService.getDirectoryType(); + if (type !== DirectoryType.AzureActiveDirectory) { + return; + } + + this.dirConfig = await this.configurationService.getDirectory( + DirectoryType.AzureActiveDirectory); + if (this.dirConfig == null) { + return; + } + + this.syncConfig = await this.configurationService.getSync(); + if (this.syncConfig == null) { + return; + } + + let users: UserEntry[]; + if (this.syncConfig.users) { + users = await this.getUsers(force); + } + + let groups: GroupEntry[]; + if (this.syncConfig.groups) { + groups = await this.getGroups(); + } + + return [groups, users]; + } + + private async getUsers(force: boolean): Promise { + const entries: UserEntry[] = []; + + let res: any = null; + const token = await this.configurationService.getUserDeltaToken(); + if (!force && token != null) { + try { + const deltaReq = this.client.api(token); + res = await deltaReq.get(); + } catch { + res = null; + } + } + + if (res == null) { + const userReq = this.client.api('/users/delta'); + res = await userReq.get(); + } + + const filter = this.createSet(this.syncConfig.userFilter); + while (true) { + const users: graphType.User[] = res.val; + if (users != null) { + users.forEach((user) => { + const entry = new UserEntry(); + entry.referenceId = user.id; + entry.externalId = user.id; + entry.email = user.mail || user.userPrincipalName; + entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled; + + if (this.filterOutResult(filter, entry.email)) { + return; + } + + if ((user as any)['@removed'] != null && (user as any)['@removed'].reason === 'changed') { + entry.deleted = true; + } else if (!entry.disabled && (entry.email == null || entry.email.indexOf('#') > -1)) { + return; + } + + entries.push(entry); + }); + } + + if (res[NextLink] == null) { + if (res[DeltaLink] != null) { + await this.configurationService.saveUserDeltaToken(res[DeltaLink]); + } + break; + } else { + const nextReq = this.client.api(res[NextLink]); + res = await nextReq.get(); + } + } + + return entries; + } + + private async getGroups(): Promise { + const entries: GroupEntry[] = []; + + const request = this.client.api('/groups/delta'); + const groups = await request.get(); + console.log(groups); + + groups.value.forEach(async (g: any) => { + const membersRequest = this.client.api('/groups/' + g.id + '/members'); + const members = await membersRequest.get(); + console.log(members); + }); + + return entries; + } + + private createSet(filter: string): [boolean, Set] { + if (filter == null || filter === '') { + return null; + } + + const parts = filter.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 init() { this.client = graph.Client.init({ authProvider: (done) => { const data = querystring.stringify({ @@ -56,45 +213,4 @@ export class AzureDirectoryService implements DirectoryService { }, }); } - - async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); - if (type !== DirectoryType.AzureActiveDirectory) { - return; - } - - this.dirConfig = await this.configurationService.getDirectory( - DirectoryType.AzureActiveDirectory); - if (this.dirConfig == null) { - return; - } - - this.syncConfig = await this.configurationService.getSync(); - if (this.syncConfig == null) { - return; - } - - await this.getUsers(); - await this.getGroups(); - - return null; - } - - private async getUsers() { - const request = this.client.api('/users/delta'); - const users = await request.get(); - console.log(users); - } - - private async getGroups() { - const request = this.client.api('/groups/delta'); - const groups = await request.get(); - console.log(groups); - - groups.value.forEach(async (g: any) => { - const membersRequest = this.client.api('/groups/' + g.id + '/members'); - const members = await membersRequest.get(); - console.log(members); - }); - } } diff --git a/src/services/configuration.service.ts b/src/services/configuration.service.ts index b100d0c7..2a30cca6 100644 --- a/src/services/configuration.service.ts +++ b/src/services/configuration.service.ts @@ -14,6 +14,8 @@ const Keys = { directoryConfigPrefix: 'directoryConfig_', sync: 'syncConfig', directoryType: 'directoryType', + userDelta: 'userDeltaToken', + groupDelta: 'groupDeltaToken', }; export class ConfigurationService { @@ -73,19 +75,48 @@ export class ConfigurationService { await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig); } - async getSync(): Promise { + getSync(): Promise { return this.storageService.get(Keys.sync); } - async saveSync(config: SyncConfiguration) { + saveSync(config: SyncConfiguration) { return this.storageService.save(Keys.sync, config); } - async getDirectoryType(): Promise { + getDirectoryType(): Promise { return this.storageService.get(Keys.directoryType); } async saveDirectoryType(type: DirectoryType) { + const currentType = await this.getDirectoryType(); + if (type !== currentType) { + await this.saveUserDeltaToken(null); + await this.saveGroupDeltaToken(null); + } return this.storageService.save(Keys.directoryType, type); } + + getUserDeltaToken(): Promise { + return this.storageService.get(Keys.userDelta); + } + + saveUserDeltaToken(token: string) { + if (token == null) { + return this.storageService.remove(Keys.userDelta); + } else { + return this.storageService.save(Keys.userDelta, token); + } + } + + getGroupDeltaToken(): Promise { + return this.storageService.get(Keys.groupDelta); + } + + saveGroupDeltaToken(token: string) { + if (token == null) { + return this.storageService.remove(Keys.groupDelta); + } else { + return this.storageService.save(Keys.groupDelta, token); + } + } }