diff --git a/src/app/tabs/settings.component.html b/src/app/tabs/settings.component.html index eca7acaf..69f96309 100644 --- a/src/app/tabs/settings.component.html +++ b/src/app/tabs/settings.component.html @@ -147,6 +147,26 @@ [(ngModel)]="okta.token"> +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/src/app/tabs/settings.component.ts b/src/app/tabs/settings.component.ts index eb1e9001..3bb434d1 100644 --- a/src/app/tabs/settings.component.ts +++ b/src/app/tabs/settings.component.ts @@ -19,6 +19,7 @@ import { AzureConfiguration } from '../../models/azureConfiguration'; import { GSuiteConfiguration } from '../../models/gsuiteConfiguration'; import { LdapConfiguration } from '../../models/ldapConfiguration'; import { OktaConfiguration } from '../../models/oktaConfiguration'; +import { OneLoginConfiguration } from '../../models/oneLoginConfiguration'; import { SyncConfiguration } from '../../models/syncConfiguration'; import { ConnectorUtils } from '../../utils'; @@ -34,6 +35,7 @@ export class SettingsComponent implements OnInit, OnDestroy { gsuite = new GSuiteConfiguration(); azure = new AzureConfiguration(); okta = new OktaConfiguration(); + oneLogin = new OneLoginConfiguration(); sync = new SyncConfiguration(); organizationId: string; directoryOptions: any[]; @@ -48,6 +50,7 @@ export class SettingsComponent implements OnInit, OnDestroy { { name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory }, { name: 'G Suite (Google)', value: DirectoryType.GSuite }, { name: 'Okta', value: DirectoryType.Okta }, + // { name: 'OneLogin', value: DirectoryType.OneLogin }, ]; } @@ -70,6 +73,8 @@ export class SettingsComponent implements OnInit, OnDestroy { DirectoryType.AzureActiveDirectory)) || this.azure; this.okta = (await this.configurationService.getDirectory( DirectoryType.Okta)) || this.okta; + this.oneLogin = (await this.configurationService.getDirectory( + DirectoryType.OneLogin)) || this.oneLogin; this.sync = (await this.configurationService.getSync()) || this.sync; } @@ -85,6 +90,7 @@ export class SettingsComponent implements OnInit, OnDestroy { await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure); await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta); + await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin); await this.configurationService.saveSync(this.sync); } diff --git a/src/enums/directoryType.ts b/src/enums/directoryType.ts index 521c78cd..4324fc06 100644 --- a/src/enums/directoryType.ts +++ b/src/enums/directoryType.ts @@ -3,4 +3,5 @@ export enum DirectoryType { AzureActiveDirectory = 1, GSuite = 2, Okta = 3, + OneLogin = 4, } diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 72a21cdc..c30c1b05 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -582,8 +582,8 @@ "message": "Hide to Tray" }, "alwaysOnTop": { - "message": "Always on Top", - "description": "Application window should always stay on top of other windows" + "message": "Always on Top", + "description": "Application window should always stay on top of other windows" }, "hideToMenuBar": { "message": "Hide to Menu Bar" @@ -599,5 +599,14 @@ }, "overwriteExisting": { "message": "Overwrite existing organization users based on current sync settings." + }, + "clientId": { + "message": "Client ID" + }, + "clientSecret": { + "message": "Client Secret" + }, + "region": { + "message": "Region" } } diff --git a/src/models/oneLoginConfiguration.ts b/src/models/oneLoginConfiguration.ts new file mode 100644 index 00000000..b9a2402f --- /dev/null +++ b/src/models/oneLoginConfiguration.ts @@ -0,0 +1,5 @@ +export class OneLoginConfiguration { + clientId: string; + clientSecret: string; + region = 'us'; +} diff --git a/src/services/configuration.service.ts b/src/services/configuration.service.ts index 71937e9b..a617da14 100644 --- a/src/services/configuration.service.ts +++ b/src/services/configuration.service.ts @@ -6,6 +6,7 @@ import { GSuiteConfiguration } from '../models/gsuiteConfiguration'; import { LdapConfiguration } from '../models/ldapConfiguration'; import { OktaConfiguration } from '../models/oktaConfiguration'; import { SyncConfiguration } from '../models/syncConfiguration'; +import { OneLoginConfiguration } from 'src/models/oneLoginConfiguration'; const StoredSecurely = '[STORED SECURELY]'; const Keys = { @@ -13,6 +14,7 @@ const Keys = { gsuite: 'gsuitePrivateKey', azure: 'azureKey', okta: 'oktaToken', + oneLogin: 'oneLoginClientSecret', directoryConfigPrefix: 'directoryConfig_', sync: 'syncConfig', directoryType: 'directoryType', @@ -48,13 +50,17 @@ export class ConfigurationService { case DirectoryType.GSuite: (config as any).privateKey = await this.secureStorageService.get(Keys.gsuite); break; + case DirectoryType.OneLogin: + (config as any).clientSecret = await this.secureStorageService.get(Keys.oneLogin); + break; } } return config; } async saveDirectory(type: DirectoryType, - config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration): Promise { + config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration | + OneLoginConfiguration): Promise { const savedConfig: any = Object.assign({}, config); if (this.useSecureStorageForSecrets) { switch (type) { @@ -92,6 +98,14 @@ export class ConfigurationService { savedConfig.privateKey = StoredSecurely; } break; + case DirectoryType.OneLogin: + if (savedConfig.clientSecret == null) { + await this.secureStorageService.remove(Keys.oneLogin); + } else { + await this.secureStorageService.save(Keys.oneLogin, savedConfig.clientSecret); + savedConfig.clientSecret = StoredSecurely; + } + break; } } await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig); diff --git a/src/services/onelogin-directory.service.ts b/src/services/onelogin-directory.service.ts new file mode 100644 index 00000000..3dda6bf4 --- /dev/null +++ b/src/services/onelogin-directory.service.ts @@ -0,0 +1,137 @@ +import { DirectoryType } from '../enums/directoryType'; + +import { GroupEntry } from '../models/groupEntry'; +import { OneLoginConfiguration } from '../models/oneLoginConfiguration'; +import { SyncConfiguration } from '../models/syncConfiguration'; +import { UserEntry } from '../models/userEntry'; + +import { BaseDirectoryService } from './baseDirectory.service'; +import { ConfigurationService } from './configuration.service'; +import { DirectoryService } from './directory.service'; + +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { LogService } from 'jslib/abstractions/log.service'; + +export class OneLoginDirectoryService extends BaseDirectoryService implements DirectoryService { + private dirConfig: OneLoginConfiguration; + private syncConfig: SyncConfiguration; + private accessToken: string; + + constructor(private configurationService: ConfigurationService, private logService: LogService, + private i18nService: I18nService) { + super(); + } + + async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { + const type = await this.configurationService.getDirectoryType(); + if (type !== DirectoryType.OneLogin) { + return; + } + + this.dirConfig = await this.configurationService.getDirectory(DirectoryType.OneLogin); + if (this.dirConfig == null) { + return; + } + + this.syncConfig = await this.configurationService.getSync(); + if (this.syncConfig == null) { + return; + } + + if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) { + throw new Error(this.i18nService.t('dirConfigIncomplete')); + } + + this.accessToken = await this.getAccessToken(); + if (this.accessToken == null) { + throw new Error('Could not get access token'); + } + + let users: UserEntry[]; + if (this.syncConfig.users) { + users = await this.getUsers(force); + } + + let groups: GroupEntry[]; + if (this.syncConfig.groups) { + const setFilter = this.createCustomSet(this.syncConfig.groupFilter); + groups = await this.getGroups(this.forceGroup(force, users), setFilter); + users = this.filterUsersFromGroupsSet(users, groups, setFilter); + } + + return [groups, users]; + } + + private async getUsers(force: boolean): Promise { + const entries: UserEntry[] = []; + const lastSync = await this.configurationService.getLastUserSyncDate(); + // const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync); + const setFilter = this.createCustomSet(this.syncConfig.userFilter); + + this.logService.info('Querying users.'); + /* + const usersPromise = this.client.listUsers({ filter: oktaFilter }).each((user: any) => { + const entry = this.buildUser(user); + if (entry != null && !this.filterOutResult(setFilter, entry.email)) { + entries.push(entry); + } + }); + */ + const users = await this.client.apis.users.get_users( + {}, + this.requestInterceptor()); + const u2 = this.getApi('users'); + return Promise.resolve([]); + } + + private async getGroups(force: boolean, setFilter: [boolean, Set]): Promise { + return Promise.resolve([]); + } + + private requestInterceptor(): any { + return { + requestInterceptor: (req: any) => { + req.headers.Authorization = 'bearer:' + this.accessToken; + return req; + }, + }; + } + + private async getAccessToken() { + const response = await fetch(`https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`, { + method: 'POST', + headers: new Headers({ + 'Authorization': 'Basic ' + btoa(this.dirConfig.clientId + ':' + this.dirConfig.clientSecret), + 'Content-Type': 'application/json; charset=utf-8', + 'Accept': 'application/json', + }), + body: JSON.stringify({ + grant_type: 'client_credentials', + }), + }); + if (response.status === 200) { + const responseJson = await response.json(); + if (responseJson.access_token != null) { + return responseJson.access_token; + } + } + return null; + } + + private async getApi(endpoint: string): Promise { + const req: RequestInit = { + method: 'GET', + headers: new Headers({ + 'Authorization': 'bearer:' + this.accessToken, + 'Accept': 'application/json', + }), + }; + const response = await fetch( + new Request(`https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`, req)); + if (response.status === 200) { + const responseJson = await response.json(); + return responseJson; + } + return null; + } +} diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts index 2c25de80..0d9d7096 100644 --- a/src/services/sync.service.ts +++ b/src/services/sync.service.ts @@ -22,6 +22,7 @@ import { DirectoryService } from './directory.service'; import { GSuiteDirectoryService } from './gsuite-directory.service'; import { LdapDirectoryService } from './ldap-directory.service'; import { OktaDirectoryService } from './okta-directory.service'; +import { OneLoginDirectoryService } from './onelogin-directory.service'; export class SyncService { private dirType: DirectoryType; @@ -123,6 +124,8 @@ export class SyncService { return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService); case DirectoryType.Okta: return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService); + case DirectoryType.OneLogin: + return new OneLoginDirectoryService(this.configurationService, this.logService, this.i18nService); default: return null; }