1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-15 15:53:41 +00:00

wire up directory services to settings

This commit is contained in:
Kyle Spearrin
2018-04-27 19:31:15 -04:00
parent 4145de7662
commit dea8e48895
10 changed files with 188 additions and 61 deletions

View File

@@ -17,6 +17,7 @@ import { LaunchGuardService } from './launch-guard.service';
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from '../../services/configuration.service';
import { I18nService } from '../../services/i18n.service'; import { I18nService } from '../../services/i18n.service';
import { SyncService } from '../../services/sync.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { ValidationService } from 'jslib/angular/services/validation.service'; import { ValidationService } from 'jslib/angular/services/validation.service';
@@ -70,6 +71,7 @@ const containerService = new ContainerService(cryptoService, platformUtilsServic
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService, const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
i18nService, platformUtilsService, messagingService, false); i18nService, platformUtilsService, messagingService, false);
const configurationService = new ConfigurationService(storageService, secureStorageService); const configurationService = new ConfigurationService(storageService, secureStorageService);
const syncSevrice = new SyncService(configurationService);
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService); const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
containerService.attachToWindow(window); containerService.attachToWindow(window);
@@ -123,6 +125,7 @@ export function initFactory(): Function {
{ provide: StateServiceAbstraction, useValue: stateService }, { provide: StateServiceAbstraction, useValue: stateService },
{ provide: LogServiceAbstraction, useValue: logService }, { provide: LogServiceAbstraction, useValue: logService },
{ provide: ConfigurationService, useValue: configurationService }, { provide: ConfigurationService, useValue: configurationService },
{ provide: SyncService, useValue: syncSevrice },
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: initFactory, useFactory: initFactory,

View File

@@ -1,7 +1,2 @@
<i class="fa fa-rocket"></i> <button (click)="sync()">Sync</button>
<button (click)="simulate()">Simulate</button>
The dashboard!!
<button (click)="gsuite()">G Suite</button>
<button (click)="ldap()">LDAP</button>
<button (click)="azuread()">Azure AD</button>

View File

@@ -15,6 +15,7 @@ import { ModalComponent } from 'jslib/angular/components/modal.component';
import { AzureDirectoryService } from '../../services/azure-directory.service'; import { AzureDirectoryService } from '../../services/azure-directory.service';
import { GSuiteDirectoryService } from '../../services/gsuite-directory.service'; import { GSuiteDirectoryService } from '../../services/gsuite-directory.service';
import { LdapDirectoryService } from '../../services/ldap-directory.service'; import { LdapDirectoryService } from '../../services/ldap-directory.service';
import { SyncService } from '../../services/sync.service';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@@ -24,20 +25,14 @@ export class DashboardComponent {
@ViewChild('settings', { read: ViewContainerRef }) settingsModal: ViewContainerRef; @ViewChild('settings', { read: ViewContainerRef }) settingsModal: ViewContainerRef;
constructor(analytics: Angulartics2, toasterService: ToasterService, constructor(analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver) { } i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private syncService: SyncService) { }
gsuite() { async sync() {
const gsuite = new GSuiteDirectoryService(); await this.syncService.sync(true, true);
const entries = gsuite.getEntries();
} }
ldap() { async simulate() {
const gsuite = new LdapDirectoryService(); await this.syncService.sync(true, false);
const entries = gsuite.getEntries();
}
azuread() {
const gsuite = new AzureDirectoryService();
const entries = gsuite.getEntries();
} }
} }

View File

@@ -85,7 +85,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="privateKey">{{'privateKey' | i18n}}</label> <label for="privateKey">{{'privateKey' | i18n}}</label>
<textarea class="form-control" id="privateKey" name="PrivateKey" [(ngModel)]="gsuite.privateKey"> <textarea class="form-control text-monospace" id="privateKey" name="PrivateKey" [(ngModel)]="gsuite.privateKey">
</textarea> </textarea>
</div> </div>
</div> </div>

View File

@@ -10,7 +10,6 @@ import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from '../../services/configuration.service';
@@ -36,7 +35,7 @@ export class SettingsComponent implements OnInit {
constructor(analytics: Angulartics2, toasterService: ToasterService, constructor(analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private configurationService: ConfigurationService, private storageService: StorageService) { private configurationService: ConfigurationService) {
this.directoryOptions = [ this.directoryOptions = [
{ name: i18nService.t('select'), value: null }, { name: i18nService.t('select'), value: null },
{ name: 'Active Directory / LDAP', value: DirectoryType.Ldap }, { name: 'Active Directory / LDAP', value: DirectoryType.Ldap },
@@ -46,21 +45,21 @@ export class SettingsComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.directory = await this.storageService.get<DirectoryType>('directory'); this.directory = await this.configurationService.getDirectoryType();
this.ldap = (await this.configurationService.get<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
this.ldap; this.ldap;
this.gsuite = (await this.configurationService.get<GSuiteConfiguration>(DirectoryType.GSuite)) || this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite; this.gsuite;
this.azure = (await this.configurationService.get<AzureConfiguration>(DirectoryType.AzureActiveDirectory)) || this.azure = (await this.configurationService.getDirectory<AzureConfiguration>(
this.azure; DirectoryType.AzureActiveDirectory)) || this.azure;
this.sync = (await this.storageService.get<SyncConfiguration>('syncConfig')) || this.sync; this.sync = (await this.configurationService.getSync()) || this.sync;
} }
async submit() { async submit() {
await this.storageService.save('directory', this.directory); await this.configurationService.saveDirectoryType(this.directory);
await this.configurationService.save(DirectoryType.Ldap, this.ldap); await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
await this.configurationService.save(DirectoryType.GSuite, this.gsuite); await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
await this.configurationService.save(DirectoryType.AzureActiveDirectory, this.azure); await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.storageService.save('syncConfig', this.sync); await this.configurationService.saveSync(this.sync);
} }
} }

View File

@@ -2,28 +2,32 @@ import * as graph from '@microsoft/microsoft-graph-client';
import * as https from 'https'; import * as https from 'https';
import * as querystring from 'querystring'; import * as querystring from 'querystring';
import { DirectoryService } from 'src/services/directory.service'; import { DirectoryType } from '../enums/directoryType';
const Key = ''; import { AzureConfiguration } from '../models/azureConfiguration';
const ApplicationId = ''; import { SyncConfiguration } from '../models/syncConfiguration';
const Tenant = '';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
export class AzureDirectoryService implements DirectoryService { export class AzureDirectoryService implements DirectoryService {
private client: graph.Client; private client: graph.Client;
private dirConfig: AzureConfiguration;
private syncConfig: SyncConfiguration;
async getEntries(force = false) { constructor(private configurationService: ConfigurationService) {
this.client = graph.Client.init({ this.client = graph.Client.init({
authProvider: (done) => { authProvider: (done) => {
const data = querystring.stringify({ const data = querystring.stringify({
client_id: ApplicationId, client_id: this.dirConfig.applicationId,
client_secret: Key, client_secret: this.dirConfig.key,
grant_type: 'client_credentials', grant_type: 'client_credentials',
scope: 'https://graph.microsoft.com/.default', scope: 'https://graph.microsoft.com/.default',
}); });
const req = https.request({ const req = https.request({
host: 'login.microsoftonline.com', host: 'login.microsoftonline.com',
path: '/' + Tenant + '/oauth2/v2.0/token', path: '/' + this.dirConfig.tenant + '/oauth2/v2.0/token',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
@@ -49,6 +53,24 @@ export class AzureDirectoryService implements DirectoryService {
req.end(); req.end();
}, },
}); });
}
async getEntries(force = false) {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.AzureActiveDirectory) {
return;
}
this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory);
if (this.dirConfig == null) {
return;
}
this.syncConfig = await this.configurationService.getSync();
if (this.syncConfig == null) {
return;
}
await this.getUsers(); await this.getUsers();
await this.getGroups(); await this.getGroups();

View File

@@ -1,6 +1,10 @@
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from '../enums/directoryType';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
import { AzureConfiguration } from '../models/azureConfiguration';
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
import { LdapConfiguration } from '../models/ldapConfiguration';
import { SyncConfiguration } from '../models/syncConfiguration';
const StoredSecurely = '[STORED SECURELY]'; const StoredSecurely = '[STORED SECURELY]';
const Keys = { const Keys = {
@@ -8,12 +12,14 @@ const Keys = {
gsuite: 'gsuitePrivateKey', gsuite: 'gsuitePrivateKey',
azure: 'azureKey', azure: 'azureKey',
directoryConfigPrefix: 'directoryConfig_', directoryConfigPrefix: 'directoryConfig_',
sync: 'syncConfig',
directoryType: 'directoryType',
}; };
export class ConfigurationService { export class ConfigurationService {
constructor(private storageService: StorageService, private secureStorageService: StorageService) { } constructor(private storageService: StorageService, private secureStorageService: StorageService) { }
async get<T>(type: DirectoryType): Promise<T> { async getDirectory<T>(type: DirectoryType): Promise<T> {
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type); const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
if (config == null) { if (config == null) {
return config; return config;
@@ -33,7 +39,8 @@ export class ConfigurationService {
return config; return config;
} }
async save<T>(type: DirectoryType, config: T): Promise<any> { async saveDirectory(type: DirectoryType,
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration): Promise<any> {
const savedConfig: any = Object.assign({}, config); const savedConfig: any = Object.assign({}, config);
switch (type) { switch (type) {
case DirectoryType.Ldap: case DirectoryType.Ldap:
@@ -56,6 +63,8 @@ export class ConfigurationService {
if (savedConfig.privateKey == null) { if (savedConfig.privateKey == null) {
await this.secureStorageService.remove(Keys.gsuite); await this.secureStorageService.remove(Keys.gsuite);
} else { } else {
(config as any).privateKey = savedConfig.privateKey =
savedConfig.privateKey.replace(/\\n/g, '\n');
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey); await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
savedConfig.privateKey = StoredSecurely; savedConfig.privateKey = StoredSecurely;
} }
@@ -63,4 +72,20 @@ export class ConfigurationService {
} }
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig); await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
} }
async getSync(): Promise<SyncConfiguration> {
return this.storageService.get<SyncConfiguration>(Keys.sync);
}
async saveSync(config: SyncConfiguration) {
return this.storageService.save(Keys.sync, config);
}
async getDirectoryType(): Promise<DirectoryType> {
return this.storageService.get<DirectoryType>(Keys.directoryType);
}
async saveDirectoryType(type: DirectoryType) {
return this.storageService.save(Keys.directoryType, type);
}
} }

View File

@@ -2,23 +2,41 @@ import { JWT } from 'google-auth-library';
import { google, GoogleApis } from 'googleapis'; import { google, GoogleApis } from 'googleapis';
import { Admin } from 'googleapis/build/src/apis/admin/directory_v1'; import { Admin } from 'googleapis/build/src/apis/admin/directory_v1';
import { DirectoryService } from 'src/services/directory.service'; import { DirectoryType } from '../enums/directoryType';
const PrivateKey = ''; import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
const ClientEmail = ''; import { SyncConfiguration } from '../models/syncConfiguration';
const AdminEmail = '';
const Domain = ''; import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
export class GSuiteDirectoryService implements DirectoryService { export class GSuiteDirectoryService implements DirectoryService {
private client: JWT; private client: JWT;
private service: Admin; private service: Admin;
private authParams: any; private authParams: any;
private dirConfig: GSuiteConfiguration;
private syncConfig: SyncConfiguration;
constructor() { constructor(private configurationService: ConfigurationService) {
this.service = google.admin<Admin>('directory_v1'); this.service = google.admin<Admin>('directory_v1');
} }
async getEntries(force = false) { async getEntries(force = false) {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.GSuite) {
return;
}
this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite);
if (this.dirConfig == null) {
return;
}
this.syncConfig = await this.configurationService.getSync();
if (this.syncConfig == null) {
return;
}
await this.auth(); await this.auth();
await this.getUsers(); await this.getUsers();
await this.getGroups(); await this.getGroups();
@@ -48,9 +66,9 @@ export class GSuiteDirectoryService implements DirectoryService {
private async auth() { private async auth() {
this.client = new google.auth.JWT({ this.client = new google.auth.JWT({
email: ClientEmail, email: this.dirConfig.clientEmail,
key: PrivateKey, key: this.dirConfig.privateKey,
subject: AdminEmail, subject: this.dirConfig.adminUser,
scopes: [ scopes: [
'https://www.googleapis.com/auth/admin.directory.user.readonly', 'https://www.googleapis.com/auth/admin.directory.user.readonly',
'https://www.googleapis.com/auth/admin.directory.group.readonly', 'https://www.googleapis.com/auth/admin.directory.group.readonly',
@@ -59,11 +77,13 @@ export class GSuiteDirectoryService implements DirectoryService {
}); });
await this.client.authorize(); await this.client.authorize();
this.authParams = { this.authParams = {
auth: this.client, auth: this.client,
domain: Domain, domain: this.dirConfig.domain,
}; };
if (this.dirConfig.customer != null) {
// TODO: add customer? this.authParams.customer = this.dirConfig.customer;
}
} }
} }

View File

@@ -1,15 +1,36 @@
import * as ldap from 'ldapjs'; import * as ldap from 'ldapjs';
import { DirectoryService } from 'src/services/directory.service'; import { DirectoryType } from '../enums/directoryType';
const Url = 'ldap://ldap.forumsys.com:389'; import { LdapConfiguration } from '../models/ldapConfiguration';
const Username = 'cn=read-only-admin,dc=example,dc=com'; import { SyncConfiguration } from '../models/syncConfiguration';
const Password = 'password';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
export class LdapDirectoryService implements DirectoryService { export class LdapDirectoryService implements DirectoryService {
private client: ldap.Client; private client: ldap.Client;
private dirConfig: LdapConfiguration;
private syncConfig: SyncConfiguration;
constructor(private configurationService: ConfigurationService) { }
async getEntries(force = false) { async getEntries(force = false) {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.Ldap) {
return;
}
this.dirConfig = await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
if (this.dirConfig == null) {
return;
}
this.syncConfig = await this.configurationService.getSync();
if (this.syncConfig == null) {
return;
}
await this.auth(); await this.auth();
await this.getUsers(); await this.getUsers();
} }
@@ -49,11 +70,13 @@ export class LdapDirectoryService implements DirectoryService {
private async auth() { private async auth() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const url = 'ldap' + (this.dirConfig.ssl ? 's' : '') + '://' + this.dirConfig.hostname +
':' + this.dirConfig.port;
this.client = ldap.createClient({ this.client = ldap.createClient({
url: Url, url: url,
}); });
this.client.bind(Username, Password, (err) => { this.client.bind(this.dirConfig.username, this.dirConfig.password, (err) => {
if (err != null) { if (err != null) {
reject(err); reject(err);
} else { } else {

View File

@@ -0,0 +1,45 @@
import { DirectoryType } from '../enums/directoryType';
import { StorageService } from 'jslib/abstractions/storage.service';
import { AzureDirectoryService } from './azure-directory.service';
import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { GSuiteDirectoryService } from './gsuite-directory.service';
import { LdapDirectoryService } from './ldap-directory.service';
const Keys = {
};
export class SyncService {
private dirType: DirectoryType;
constructor(private configurationService: ConfigurationService) { }
async sync(force = true, sendToServer = true): Promise<any> {
this.dirType = await this.configurationService.getDirectoryType();
if (this.dirType == null) {
return;
}
const directoryService = this.getDirectoryService();
if (directoryService == null) {
return;
}
directoryService.getEntries(force);
}
private getDirectoryService(): DirectoryService {
switch (this.dirType) {
case DirectoryType.GSuite:
return new GSuiteDirectoryService(this.configurationService);
case DirectoryType.AzureActiveDirectory:
return new AzureDirectoryService(this.configurationService);
case DirectoryType.Ldap:
return new LdapDirectoryService(this.configurationService);
default:
return null;
}
}
}