mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-16 16:23:41 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f53c1f5605 | ||
|
|
f0f7f89ea8 | ||
|
|
059ff0647a | ||
|
|
d94e5b0620 | ||
|
|
71cd11eedf | ||
|
|
ecea04bc08 | ||
|
|
0575ee5507 | ||
|
|
8b2fa8405b | ||
|
|
7d36a50687 | ||
|
|
f7dd9d8d5b | ||
|
|
379b4f4612 | ||
|
|
4652668e1b | ||
|
|
4e02a8571e | ||
|
|
bc927a65ac | ||
|
|
90f1a1c115 | ||
|
|
c48acf6038 | ||
|
|
2640e8c890 | ||
|
|
094ec55e7f | ||
|
|
fe39bdac42 | ||
|
|
2c98d50c43 | ||
|
|
d374cff51c | ||
|
|
d9e7256804 | ||
|
|
0ef2d1523e | ||
|
|
05bad6f671 | ||
|
|
5a62cfcda1 | ||
|
|
634d38510d | ||
|
|
bf27872973 | ||
|
|
20bb5a4926 | ||
|
|
f63fb3ffa0 | ||
|
|
f11c32c606 | ||
|
|
0b4e22a952 | ||
|
|
a85dbff3a5 | ||
|
|
d2835dd577 | ||
|
|
62abc99b61 | ||
|
|
20de62cc79 | ||
|
|
731614279f | ||
|
|
2be751dc8e | ||
|
|
e0409941a3 |
2
jslib
2
jslib
Submodule jslib updated: b74ee7b3ee...212a2e3745
@@ -12,14 +12,14 @@ BLOCK "StringFileInfo"
|
|||||||
{
|
{
|
||||||
BLOCK "040904b0"
|
BLOCK "040904b0"
|
||||||
{
|
{
|
||||||
VALUE "CompanyName", "8bit Solutions LLC"
|
VALUE "CompanyName", "Bitwarden Inc."
|
||||||
VALUE "ProductName", "Bitwarden"
|
VALUE "ProductName", "Bitwarden"
|
||||||
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
||||||
VALUE "FileVersion", "$env:PACKAGE_VERSION"
|
VALUE "FileVersion", "$env:PACKAGE_VERSION"
|
||||||
VALUE "ProductVersion", "$env:PACKAGE_VERSION"
|
VALUE "ProductVersion", "$env:PACKAGE_VERSION"
|
||||||
VALUE "OriginalFilename", "bwdc.exe"
|
VALUE "OriginalFilename", "bwdc.exe"
|
||||||
VALUE "InternalName", "bwdc"
|
VALUE "InternalName", "bwdc"
|
||||||
VALUE "LegalCopyright", "Copyright 8bit Solutions LLC"
|
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4229
package-lock.json
generated
4229
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -9,7 +9,7 @@
|
|||||||
"vault",
|
"vault",
|
||||||
"password manager"
|
"password manager"
|
||||||
],
|
],
|
||||||
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -22,7 +22,9 @@
|
|||||||
"sub:pull": "git submodule foreach git pull origin master",
|
"sub:pull": "git submodule foreach git pull origin master",
|
||||||
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
||||||
"postinstall": "npm run sub:init",
|
"postinstall": "npm run sub:init",
|
||||||
"simlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
||||||
|
"symlink:mac": "npm run symlink:lin",
|
||||||
|
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||||
"rebuild": "./node_modules/.bin/electron-rebuild",
|
"rebuild": "./node_modules/.bin/electron-rebuild",
|
||||||
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
||||||
"lint": "tslint src/**/*.ts || true",
|
"lint": "tslint src/**/*.ts || true",
|
||||||
@@ -61,7 +63,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.bitwarden.directory-connector",
|
"appId": "com.bitwarden.directory-connector",
|
||||||
"copyright": "Copyright © 2015-2018 8bit Solutions LLC",
|
"copyright": "Copyright © 2015-2020 Bitwarden Inc.",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "resources",
|
"buildResources": "resources",
|
||||||
"output": "dist",
|
"output": "dist",
|
||||||
@@ -143,7 +145,7 @@
|
|||||||
"@types/inquirer": "^0.0.43",
|
"@types/inquirer": "^0.0.43",
|
||||||
"@types/ldapjs": "^1.0.3",
|
"@types/ldapjs": "^1.0.3",
|
||||||
"@types/lowdb": "^1.0.5",
|
"@types/lowdb": "^1.0.5",
|
||||||
"@types/lunr": "^2.1.6",
|
"@types/lunr": "^2.3.3",
|
||||||
"@types/node": "^10.9.4",
|
"@types/node": "^10.9.4",
|
||||||
"@types/node-fetch": "^2.1.2",
|
"@types/node-fetch": "^2.1.2",
|
||||||
"@types/node-forge": "^0.7.5",
|
"@types/node-forge": "^0.7.5",
|
||||||
@@ -159,12 +161,12 @@
|
|||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"electron": "5.0.8",
|
"electron": "6.1.7",
|
||||||
"electron-builder": "21.1.5",
|
"electron-builder": "22.4.0",
|
||||||
"electron-notarize": "^0.1.1",
|
"electron-notarize": "^0.2.1",
|
||||||
"electron-rebuild": "^1.8.5",
|
"electron-rebuild": "^1.9.0",
|
||||||
"electron-reload": "^1.4.1",
|
"electron-reload": "^1.5.0",
|
||||||
"extract-text-webpack-plugin": "next",
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
@@ -173,7 +175,7 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"node-abi": "^2.9.0",
|
"node-abi": "^2.9.0",
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^0.6.0",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.13.1",
|
||||||
"pkg": "4.3.4",
|
"pkg": "4.3.4",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
@@ -192,13 +194,12 @@
|
|||||||
"@angular/compiler": "7.2.1",
|
"@angular/compiler": "7.2.1",
|
||||||
"@angular/core": "7.2.1",
|
"@angular/core": "7.2.1",
|
||||||
"@angular/forms": "7.2.1",
|
"@angular/forms": "7.2.1",
|
||||||
"@angular/http": "7.2.1",
|
|
||||||
"@angular/platform-browser": "7.2.1",
|
"@angular/platform-browser": "7.2.1",
|
||||||
"@angular/platform-browser-dynamic": "7.2.1",
|
"@angular/platform-browser-dynamic": "7.2.1",
|
||||||
"@angular/router": "7.2.1",
|
"@angular/router": "7.2.1",
|
||||||
"@angular/upgrade": "7.2.1",
|
"@angular/upgrade": "7.2.1",
|
||||||
"@microsoft/microsoft-graph-client": "1.2.0",
|
"@microsoft/microsoft-graph-client": "1.2.0",
|
||||||
"@okta/okta-sdk-nodejs": "1.2.0",
|
"@okta/okta-sdk-nodejs": "2.0.1",
|
||||||
"angular2-toaster": "6.1.0",
|
"angular2-toaster": "6.1.0",
|
||||||
"angulartics2": "6.3.0",
|
"angulartics2": "6.3.0",
|
||||||
"big-integer": "1.6.36",
|
"big-integer": "1.6.36",
|
||||||
@@ -209,10 +210,10 @@
|
|||||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||||
"electron-log": "2.2.17",
|
"electron-log": "2.2.17",
|
||||||
"electron-store": "1.3.0",
|
"electron-store": "1.3.0",
|
||||||
"electron-updater": "4.1.2",
|
"electron-updater": "4.2.0",
|
||||||
"form-data": "2.3.2",
|
"form-data": "2.3.2",
|
||||||
"googleapis": "33.0.0",
|
"googleapis": "43.0.0",
|
||||||
"https-proxy-agent": "2.2.1",
|
"https-proxy-agent": "4.0.0",
|
||||||
"inquirer": "6.2.0",
|
"inquirer": "6.2.0",
|
||||||
"keytar": "4.13.0",
|
"keytar": "4.13.0",
|
||||||
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
import { CalloutComponent } from 'jslib/angular/components/callout.component';
|
||||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CalloutComponent,
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{{'bitwardenDirectoryConnector' | i18n}}
|
{{'bitwardenDirectoryConnector' | i18n}}
|
||||||
<br /> {{'version' | i18n : version}}
|
<br /> {{'version' | i18n : version}}
|
||||||
<br /> © 8bit Solutions LLC 2015-{{year}}
|
<br /> © Bitwarden Inc. LLC 2015-{{year}}
|
||||||
</p>
|
</p>
|
||||||
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
|
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
|
||||||
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
|
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
|
||||||
|
|||||||
@@ -33,13 +33,45 @@
|
|||||||
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label>
|
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" *ngIf="!ldap.ad">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="pagedSearch"
|
||||||
|
[(ngModel)]="ldap.pagedSearch" name="PagedSearch">
|
||||||
|
<label class="form-check-label"
|
||||||
|
for="pagedSearch">{{'ldapPagedResults' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="ssl" [(ngModel)]="ldap.ssl" name="SSL">
|
<input class="form-check-input" type="checkbox" id="ldapEncrypted" [(ngModel)]="ldap.ssl"
|
||||||
<label class="form-check-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
name="Encrypted">
|
||||||
|
<label class="form-check-label" for="ldapEncrypted">{{'ldapEncrypted' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4" *ngIf="ldap.ssl">
|
<div class="ml-4" *ngIf="ldap.ssl">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-radio">
|
||||||
|
<input class="form-radio-input" type="radio" [value]="false" id="ssl"
|
||||||
|
[(ngModel)]="ldap.startTls" name="SSL">
|
||||||
|
<label class="form-radio-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-radio">
|
||||||
|
<input class="form-radio-input" type="radio" [value]="true" id="startTls"
|
||||||
|
[(ngModel)]="ldap.startTls" name="StartTLS">
|
||||||
|
<label class="form-radio-label" for="startTls">{{'ldapTls' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4" *ngIf="ldap.startTls">
|
||||||
|
<p>{{'ldapTlsUntrustedDesc' | i18n}}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tlsCaPath">{{'ldapTlsCa' | i18n}}</label>
|
||||||
|
<input type="file" class="form-control-file mb-2" id="tlsCaPath_file"
|
||||||
|
(change)="setSslPath('tlsCaPath')">
|
||||||
|
<input type="text" class="form-control" id="tlsCaPath" name="TLSCaPath"
|
||||||
|
[(ngModel)]="ldap.tlsCaPath">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4" *ngIf="!ldap.startTls">
|
||||||
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
||||||
@@ -62,12 +94,13 @@
|
|||||||
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
||||||
[(ngModel)]="ldap.sslCaPath">
|
[(ngModel)]="ldap.sslCaPath">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="sslAllowUnauthorized"
|
<input class="form-check-input" type="checkbox" id="certDoNotVerify"
|
||||||
[(ngModel)]="ldap.sslAllowUnauthorized" name="SSLAllowUnauthorized">
|
[(ngModel)]="ldap.sslAllowUnauthorized" name="CertDoNoVerify">
|
||||||
<label class="form-check-label"
|
<label class="form-check-label"
|
||||||
for="sslAllowUnauthorized">{{'ldapSslAllowUnauthorized' | i18n}}</label>
|
for="certDoNotVerify">{{'ldapCertDoNotVerify' | i18n}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +155,26 @@
|
|||||||
[(ngModel)]="okta.token">
|
[(ngModel)]="okta.token">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div [hidden]="directory != directoryType.OneLogin">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginClientId">{{'clientId' | i18n}}</label>
|
||||||
|
<input type="text" class="form-control" id="oneLoginClientId" name="OneLoginClientId"
|
||||||
|
[(ngModel)]="oneLogin.clientId">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label>
|
||||||
|
<input type="text" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
|
||||||
|
[(ngModel)]="oneLogin.clientSecret">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oneLoginRegion">{{'region' | i18n}}</label>
|
||||||
|
<select class="form-control" id="oneLoginRegion" name="OneLoginRegion"
|
||||||
|
[(ngModel)]="oneLogin.region">
|
||||||
|
<option value="us">US</option>
|
||||||
|
<option value="eu">EU</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div [hidden]="directory != directoryType.GSuite">
|
<div [hidden]="directory != directoryType.GSuite">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domain">{{'domain' | i18n}}</label>
|
<label for="domain">{{'domain' | i18n}}</label>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { AzureConfiguration } from '../../models/azureConfiguration';
|
|||||||
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
|
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
|
||||||
import { LdapConfiguration } from '../../models/ldapConfiguration';
|
import { LdapConfiguration } from '../../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../../models/oktaConfiguration';
|
import { OktaConfiguration } from '../../models/oktaConfiguration';
|
||||||
|
import { OneLoginConfiguration } from '../../models/oneLoginConfiguration';
|
||||||
import { SyncConfiguration } from '../../models/syncConfiguration';
|
import { SyncConfiguration } from '../../models/syncConfiguration';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../../utils';
|
import { ConnectorUtils } from '../../utils';
|
||||||
@@ -34,6 +35,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
gsuite = new GSuiteConfiguration();
|
gsuite = new GSuiteConfiguration();
|
||||||
azure = new AzureConfiguration();
|
azure = new AzureConfiguration();
|
||||||
okta = new OktaConfiguration();
|
okta = new OktaConfiguration();
|
||||||
|
oneLogin = new OneLoginConfiguration();
|
||||||
sync = new SyncConfiguration();
|
sync = new SyncConfiguration();
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
directoryOptions: any[];
|
directoryOptions: any[];
|
||||||
@@ -48,6 +50,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
|
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
|
||||||
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
|
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
|
||||||
{ name: 'Okta', value: DirectoryType.Okta },
|
{ name: 'Okta', value: DirectoryType.Okta },
|
||||||
|
{ name: 'OneLogin', value: DirectoryType.OneLogin },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +73,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
DirectoryType.AzureActiveDirectory)) || this.azure;
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
||||||
DirectoryType.Okta)) || this.okta;
|
DirectoryType.Okta)) || this.okta;
|
||||||
|
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
||||||
|
DirectoryType.OneLogin)) || this.oneLogin;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.sync = (await this.configurationService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +84,16 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
||||||
|
if (this.ldap != null && this.ldap.ad) {
|
||||||
|
this.ldap.pagedSearch = true;
|
||||||
|
}
|
||||||
await this.configurationService.saveOrganizationId(this.organizationId);
|
await this.configurationService.saveOrganizationId(this.organizationId);
|
||||||
await this.configurationService.saveDirectoryType(this.directory);
|
await this.configurationService.saveDirectoryType(this.directory);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
||||||
|
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.configurationService.saveSync(this.sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { AzureConfiguration } from '../models/azureConfiguration';
|
|||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
import { LdapConfiguration } from '../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
import { OktaConfiguration } from '../models/oktaConfiguration';
|
||||||
|
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
|
||||||
import { ConnectorUtils } from '../utils';
|
import { ConnectorUtils } from '../utils';
|
||||||
@@ -24,6 +25,7 @@ export class ConfigCommand {
|
|||||||
private gsuite = new GSuiteConfiguration();
|
private gsuite = new GSuiteConfiguration();
|
||||||
private azure = new AzureConfiguration();
|
private azure = new AzureConfiguration();
|
||||||
private okta = new OktaConfiguration();
|
private okta = new OktaConfiguration();
|
||||||
|
private oneLogin = new OneLoginConfiguration();
|
||||||
private sync = new SyncConfiguration();
|
private sync = new SyncConfiguration();
|
||||||
|
|
||||||
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
|
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
|
||||||
@@ -51,6 +53,9 @@ export class ConfigCommand {
|
|||||||
case 'okta.token':
|
case 'okta.token':
|
||||||
await this.setOktaToken(value);
|
await this.setOktaToken(value);
|
||||||
break;
|
break;
|
||||||
|
case 'onelogin.secret':
|
||||||
|
await this.setOneLoginSecret(value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return Response.badRequest('Unknown setting.');
|
return Response.badRequest('Unknown setting.');
|
||||||
}
|
}
|
||||||
@@ -70,7 +75,7 @@ export class ConfigCommand {
|
|||||||
|
|
||||||
private async setDirectory(type: string) {
|
private async setDirectory(type: string) {
|
||||||
const dir = parseInt(type, null);
|
const dir = parseInt(type, null);
|
||||||
if (dir < DirectoryType.Ldap || dir > DirectoryType.Okta) {
|
if (dir < DirectoryType.Ldap || dir > DirectoryType.OneLogin) {
|
||||||
throw new Error('Invalid directory type value.');
|
throw new Error('Invalid directory type value.');
|
||||||
}
|
}
|
||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
@@ -102,6 +107,12 @@ export class ConfigCommand {
|
|||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setOneLoginSecret(secret: string) {
|
||||||
|
await this.loadConfig();
|
||||||
|
this.oneLogin.clientSecret = secret;
|
||||||
|
await this.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
private async loadConfig() {
|
private async loadConfig() {
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.configurationService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
||||||
@@ -112,6 +123,8 @@ export class ConfigCommand {
|
|||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
DirectoryType.AzureActiveDirectory)) || this.azure;
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
||||||
DirectoryType.Okta)) || this.okta;
|
DirectoryType.Okta)) || this.okta;
|
||||||
|
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
||||||
|
DirectoryType.OneLogin)) || this.oneLogin;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.sync = (await this.configurationService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +135,7 @@ export class ConfigCommand {
|
|||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
||||||
|
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.configurationService.saveSync(this.sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export enum DirectoryType {
|
|||||||
AzureActiveDirectory = 1,
|
AzureActiveDirectory = 1,
|
||||||
GSuite = 2,
|
GSuite = 2,
|
||||||
Okta = 3,
|
Okta = 3,
|
||||||
|
OneLogin = 4,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,8 +414,20 @@
|
|||||||
"sync": {
|
"sync": {
|
||||||
"message": "Sync"
|
"message": "Sync"
|
||||||
},
|
},
|
||||||
|
"ldapEncrypted": {
|
||||||
|
"message": "This server uses an encrypted connection"
|
||||||
|
},
|
||||||
|
"ldapTls": {
|
||||||
|
"message": "Use TLS (STARTTLS)"
|
||||||
|
},
|
||||||
|
"ldapTlsCa": {
|
||||||
|
"message": "Certificate CA Chain (PEM)"
|
||||||
|
},
|
||||||
"ldapSsl": {
|
"ldapSsl": {
|
||||||
"message": "This server uses SSL (LDAPS)"
|
"message": "Use SSL (LDAPS)"
|
||||||
|
},
|
||||||
|
"ldapTlsUntrustedDesc": {
|
||||||
|
"message": "If your LDAP server uses a self-signed certificate for STARTTLS, you can configure certificate options below."
|
||||||
},
|
},
|
||||||
"ldapSslUntrustedDesc": {
|
"ldapSslUntrustedDesc": {
|
||||||
"message": "If your LDAPS server uses an untrusted certificate you can configure certificate options below."
|
"message": "If your LDAPS server uses an untrusted certificate you can configure certificate options below."
|
||||||
@@ -429,12 +441,15 @@
|
|||||||
"ldapSslKey": {
|
"ldapSslKey": {
|
||||||
"message": "Certificate Private Key (PEM)"
|
"message": "Certificate Private Key (PEM)"
|
||||||
},
|
},
|
||||||
"ldapSslAllowUnauthorized": {
|
"ldapCertDoNotVerify": {
|
||||||
"message": "Allow untrusted SSL connections (not recommended)."
|
"message": "Do not verify server certificates (not recommended)."
|
||||||
},
|
},
|
||||||
"ldapAd": {
|
"ldapAd": {
|
||||||
"message": "This server uses Active Directory"
|
"message": "This server uses Active Directory"
|
||||||
},
|
},
|
||||||
|
"ldapPagedResults": {
|
||||||
|
"message": "This server pages search results"
|
||||||
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"message": "Select"
|
"message": "Select"
|
||||||
},
|
},
|
||||||
@@ -587,5 +602,14 @@
|
|||||||
},
|
},
|
||||||
"overwriteExisting": {
|
"overwriteExisting": {
|
||||||
"message": "Overwrite existing organization users based on current sync settings."
|
"message": "Overwrite existing organization users based on current sync settings."
|
||||||
|
},
|
||||||
|
"clientId": {
|
||||||
|
"message": "Client ID"
|
||||||
|
},
|
||||||
|
"clientSecret": {
|
||||||
|
"message": "Client Secret"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"message": "Region"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export class LdapConfiguration {
|
export class LdapConfiguration {
|
||||||
ssl = false;
|
ssl = false;
|
||||||
|
startTls = false;
|
||||||
|
tlsCaPath: string;
|
||||||
sslAllowUnauthorized = false;
|
sslAllowUnauthorized = false;
|
||||||
sslCertPath: string;
|
sslCertPath: string;
|
||||||
sslKeyPath: string;
|
sslKeyPath: string;
|
||||||
@@ -12,4 +14,5 @@ export class LdapConfiguration {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
ad = true;
|
ad = true;
|
||||||
|
pagedSearch = true;
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/models/oneLoginConfiguration.ts
Normal file
5
src/models/oneLoginConfiguration.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class OneLoginConfiguration {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
region = 'us';
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "bitwarden-directory-connector",
|
"name": "bitwarden-directory-connector",
|
||||||
"productName": "Bitwarden Directory Connector",
|
"productName": "Bitwarden Directory Connector",
|
||||||
"description": "Sync your user directory to your Bitwarden organization.",
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
"version": "2.6.2",
|
"version": "2.7.0",
|
||||||
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-log": "2.2.17",
|
"electron-log": "2.2.17",
|
||||||
"electron-store": "1.3.0",
|
"electron-store": "1.3.0",
|
||||||
"electron-updater": "4.1.2",
|
"electron-updater": "4.2.0",
|
||||||
"keytar": "4.13.0"
|
"keytar": "4.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export class Program extends BaseProgram {
|
|||||||
.option('--raw', 'Return raw output instead of a descriptive message.')
|
.option('--raw', 'Return raw output instead of a descriptive message.')
|
||||||
.option('--response', 'Return a JSON formatted version of response output.')
|
.option('--response', 'Return a JSON formatted version of response output.')
|
||||||
.option('--quiet', 'Don\'t return anything to stdout.')
|
.option('--quiet', 'Don\'t return anything to stdout.')
|
||||||
|
.option('--nointeraction', 'Do not prompt for interactive user input.')
|
||||||
.version(this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
|
.version(this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
|
||||||
|
|
||||||
program.on('option:pretty', () => {
|
program.on('option:pretty', () => {
|
||||||
@@ -58,6 +59,10 @@ export class Program extends BaseProgram {
|
|||||||
process.env.BW_RESPONSE = 'true';
|
process.env.BW_RESPONSE = 'true';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program.on('option:nointeraction', () => {
|
||||||
|
process.env.BW_NOINTERACTION = 'true';
|
||||||
|
});
|
||||||
|
|
||||||
program.on('command:*', () => {
|
program.on('command:*', () => {
|
||||||
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true);
|
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true);
|
||||||
writeLn('See --help for a list of available commands.', true, true);
|
writeLn('See --help for a list of available commands.', true, true);
|
||||||
@@ -184,6 +189,7 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' azure.key - The Azure AD secret key.');
|
writeLn(' azure.key - The Azure AD secret key.');
|
||||||
writeLn(' gsuite.key - The G Suite private key.');
|
writeLn(' gsuite.key - The G Suite private key.');
|
||||||
writeLn(' okta.token - The Okta token.');
|
writeLn(' okta.token - The Okta token.');
|
||||||
|
writeLn(' onelogin.secret - The OneLogin client secret.');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
writeLn(' Examples:');
|
writeLn(' Examples:');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
@@ -194,6 +200,7 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bwdc config azure.key <key>');
|
writeLn(' bwdc config azure.key <key>');
|
||||||
writeLn(' bwdc config gsuite.key <key>');
|
writeLn(' bwdc config gsuite.key <key>');
|
||||||
writeLn(' bwdc config okta.token <token>');
|
writeLn(' bwdc config okta.token <token>');
|
||||||
|
writeLn(' bwdc config onelogin.secret <secret>');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (setting, value, cmd) => {
|
.action(async (setting, value, cmd) => {
|
||||||
|
|||||||
2
src/scss/bootstrap.scss
vendored
2
src/scss/bootstrap.scss
vendored
@@ -1,4 +1,4 @@
|
|||||||
$theme-colors: ( "primary": #3c8dbc, "primary-accent": #286090, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16);
|
$theme-colors: ( "primary": #175DDC, "primary-accent": #1252A3, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16);
|
||||||
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
|
||||||
$h1-font-size: 2rem;
|
$h1-font-size: 2rem;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { LogService } from 'jslib/abstractions/log.service';
|
|||||||
const NextLink = '@odata.nextLink';
|
const NextLink = '@odata.nextLink';
|
||||||
const DeltaLink = '@odata.deltaLink';
|
const DeltaLink = '@odata.deltaLink';
|
||||||
const ObjectType = '@odata.type';
|
const ObjectType = '@odata.type';
|
||||||
|
const UserSelectParams = '?$select=id,mail,userPrincipalName,displayName,accountEnabled';
|
||||||
|
|
||||||
enum UserSetType {
|
enum UserSetType {
|
||||||
IncludeUser,
|
IncludeUser,
|
||||||
@@ -78,7 +79,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
private async getCurrentUsers(): Promise<UserEntry[]> {
|
private async getCurrentUsers(): Promise<UserEntry[]> {
|
||||||
const entryIds = new Set<string>();
|
const entryIds = new Set<string>();
|
||||||
const entries: UserEntry[] = [];
|
const entries: UserEntry[] = [];
|
||||||
const userReq = this.client.api('/users');
|
const userReq = this.client.api('/users' + UserSelectParams);
|
||||||
let res = await userReq.get();
|
let res = await userReq.get();
|
||||||
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -130,7 +131,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
const userReq = this.client.api('/users/delta');
|
const userReq = this.client.api('/users/delta' + UserSelectParams);
|
||||||
res = await userReq.get();
|
res = await userReq.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +306,8 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
entry.name = group.displayName;
|
entry.name = group.displayName;
|
||||||
|
|
||||||
const memReq = this.client.api('/groups/' + group.id + '/members');
|
const memReq = this.client.api('/groups/' + group.id + '/members');
|
||||||
const memRes = await memReq.get();
|
let memRes = await memReq.get();
|
||||||
|
while (true) {
|
||||||
const members: any = memRes.value;
|
const members: any = memRes.value;
|
||||||
if (members != null) {
|
if (members != null) {
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
@@ -316,6 +318,13 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (memRes[NextLink] == null) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const nextMemReq = this.client.api(memRes[NextLink]);
|
||||||
|
memRes = await nextMemReq.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
|||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
import { LdapConfiguration } from '../models/ldapConfiguration';
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
import { OktaConfiguration } from '../models/oktaConfiguration';
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
import { OneLoginConfiguration } from 'src/models/oneLoginConfiguration';
|
||||||
|
|
||||||
const StoredSecurely = '[STORED SECURELY]';
|
const StoredSecurely = '[STORED SECURELY]';
|
||||||
const Keys = {
|
const Keys = {
|
||||||
@@ -13,6 +14,7 @@ const Keys = {
|
|||||||
gsuite: 'gsuitePrivateKey',
|
gsuite: 'gsuitePrivateKey',
|
||||||
azure: 'azureKey',
|
azure: 'azureKey',
|
||||||
okta: 'oktaToken',
|
okta: 'oktaToken',
|
||||||
|
oneLogin: 'oneLoginClientSecret',
|
||||||
directoryConfigPrefix: 'directoryConfig_',
|
directoryConfigPrefix: 'directoryConfig_',
|
||||||
sync: 'syncConfig',
|
sync: 'syncConfig',
|
||||||
directoryType: 'directoryType',
|
directoryType: 'directoryType',
|
||||||
@@ -48,13 +50,17 @@ export class ConfigurationService {
|
|||||||
case DirectoryType.GSuite:
|
case DirectoryType.GSuite:
|
||||||
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
||||||
break;
|
break;
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
(config as any).clientSecret = await this.secureStorageService.get<string>(Keys.oneLogin);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveDirectory(type: DirectoryType,
|
async saveDirectory(type: DirectoryType,
|
||||||
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration): Promise<any> {
|
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration |
|
||||||
|
OneLoginConfiguration): Promise<any> {
|
||||||
const savedConfig: any = Object.assign({}, config);
|
const savedConfig: any = Object.assign({}, config);
|
||||||
if (this.useSecureStorageForSecrets) {
|
if (this.useSecureStorageForSecrets) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -92,6 +98,14 @@ export class ConfigurationService {
|
|||||||
savedConfig.privateKey = StoredSecurely;
|
savedConfig.privateKey = StoredSecurely;
|
||||||
}
|
}
|
||||||
break;
|
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);
|
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private makeSearchPath(pathPrefix: string) {
|
private makeSearchPath(pathPrefix: string) {
|
||||||
|
if (this.dirConfig.rootPath.indexOf('dc=') === -1) {
|
||||||
|
return pathPrefix;
|
||||||
|
}
|
||||||
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== '') {
|
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== '') {
|
||||||
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
||||||
let path = trimmedRootPath.substr(trimmedRootPath.indexOf('dc='));
|
let path = trimmedRootPath.substr(trimmedRootPath.indexOf('dc='));
|
||||||
@@ -290,7 +293,7 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
const options: ldap.SearchOptions = {
|
const options: ldap.SearchOptions = {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
paged: true,
|
paged: this.dirConfig.pagedSearch,
|
||||||
};
|
};
|
||||||
const entries: T[] = [];
|
const entries: T[] = [];
|
||||||
return new Promise<T[]>((resolve, reject) => {
|
return new Promise<T[]>((resolve, reject) => {
|
||||||
@@ -324,17 +327,19 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
reject(this.i18nService.t('dirConfigIncomplete'));
|
reject(this.i18nService.t('dirConfigIncomplete'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const protocol = 'ldap' + (this.dirConfig.ssl && !this.dirConfig.startTls ? 's' : '');
|
||||||
const url = 'ldap' + (this.dirConfig.ssl ? 's' : '') + '://' + this.dirConfig.hostname +
|
const url = protocol + '://' + this.dirConfig.hostname +
|
||||||
':' + this.dirConfig.port;
|
':' + this.dirConfig.port;
|
||||||
const options: ldap.ClientOptions = {
|
const options: ldap.ClientOptions = {
|
||||||
url: url.trim().toLowerCase(),
|
url: url.trim().toLowerCase(),
|
||||||
};
|
};
|
||||||
if (this.dirConfig.ssl) {
|
|
||||||
const tlsOptions: any = {};
|
const tlsOptions: any = {};
|
||||||
if (this.dirConfig.sslAllowUnauthorized != null) {
|
if (this.dirConfig.ssl) {
|
||||||
|
if (this.dirConfig.sslAllowUnauthorized) {
|
||||||
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
||||||
}
|
}
|
||||||
|
if (!this.dirConfig.startTls) {
|
||||||
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
||||||
fs.existsSync(this.dirConfig.sslCaPath)) {
|
fs.existsSync(this.dirConfig.sslCaPath)) {
|
||||||
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
||||||
@@ -347,10 +352,17 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
||||||
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (this.dirConfig.tlsCaPath != null && this.dirConfig.tlsCaPath !== '' &&
|
||||||
|
fs.existsSync(this.dirConfig.tlsCaPath)) {
|
||||||
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(tlsOptions).length > 0) {
|
if (Object.keys(tlsOptions).length > 0) {
|
||||||
options.tlsOptions = tlsOptions;
|
options.tlsOptions = tlsOptions;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.client = ldap.createClient(options);
|
this.client = ldap.createClient(options);
|
||||||
|
|
||||||
@@ -364,6 +376,21 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.dirConfig.startTls && this.dirConfig.ssl) {
|
||||||
|
this.client.starttls(options.tlsOptions, undefined, (err, res) => {
|
||||||
|
if (err != null) {
|
||||||
|
reject(err.message);
|
||||||
|
} else {
|
||||||
|
this.client.bind(user, pass, (err2) => {
|
||||||
|
if (err2 != null) {
|
||||||
|
reject(err2.message);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
this.client.bind(user, pass, (err) => {
|
this.client.bind(user, pass, (err) => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
@@ -371,6 +398,7 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
176
src/services/onelogin-directory.service.ts
Normal file
176
src/services/onelogin-directory.service.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
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;
|
||||||
|
private allUsers: any[] = [];
|
||||||
|
|
||||||
|
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<OneLoginConfiguration>(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<UserEntry[]> {
|
||||||
|
const entries: UserEntry[] = [];
|
||||||
|
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
|
||||||
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
this.logService.info('Querying users.');
|
||||||
|
this.allUsers = await this.apiGetMany('users' + (query != null ? '?' + query : ''));
|
||||||
|
this.allUsers.forEach((user) => {
|
||||||
|
const entry = this.buildUser(user);
|
||||||
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildUser(user: any) {
|
||||||
|
const entry = new UserEntry();
|
||||||
|
entry.externalId = user.id;
|
||||||
|
entry.referenceId = user.id;
|
||||||
|
entry.email = user.email != null ? user.email.trim().toLowerCase() : null;
|
||||||
|
entry.deleted = false;
|
||||||
|
entry.disabled = user.status === 2;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
||||||
|
const entries: GroupEntry[] = [];
|
||||||
|
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
|
||||||
|
this.logService.info('Querying groups.');
|
||||||
|
const roles = await this.apiGetMany('roles' + (query != null ? '?' + query : ''));
|
||||||
|
roles.forEach((role) => {
|
||||||
|
const entry = this.buildGroup(role);
|
||||||
|
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildGroup(group: any) {
|
||||||
|
const entry = new GroupEntry();
|
||||||
|
entry.externalId = group.id;
|
||||||
|
entry.referenceId = group.id;
|
||||||
|
entry.name = group.name;
|
||||||
|
|
||||||
|
if (this.allUsers != null) {
|
||||||
|
this.allUsers.forEach((user) => {
|
||||||
|
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
|
||||||
|
entry.userMemberExternalIds.add(user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 apiGetCall(url: string): Promise<any> {
|
||||||
|
const req: RequestInit = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: new Headers({
|
||||||
|
'Authorization': 'bearer:' + this.accessToken,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const response = await fetch(
|
||||||
|
new Request(url, req));
|
||||||
|
if (response.status === 200) {
|
||||||
|
const responseJson = await response.json();
|
||||||
|
return responseJson;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
|
const url = endpoint.indexOf('https://') === 0 ? endpoint :
|
||||||
|
`https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`;
|
||||||
|
const response = await this.apiGetCall(url);
|
||||||
|
if (response == null || response.status == null || response.data == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
if (response.status.code !== 200) {
|
||||||
|
throw new Error('API call failed.');
|
||||||
|
}
|
||||||
|
currentData = currentData.concat(response.data);
|
||||||
|
if (response.pagination == null || response.pagination.next_link == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
return this.apiGetMany(response.pagination.next_link, currentData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import { DirectoryService } from './directory.service';
|
|||||||
import { GSuiteDirectoryService } from './gsuite-directory.service';
|
import { GSuiteDirectoryService } from './gsuite-directory.service';
|
||||||
import { LdapDirectoryService } from './ldap-directory.service';
|
import { LdapDirectoryService } from './ldap-directory.service';
|
||||||
import { OktaDirectoryService } from './okta-directory.service';
|
import { OktaDirectoryService } from './okta-directory.service';
|
||||||
|
import { OneLoginDirectoryService } from './onelogin-directory.service';
|
||||||
|
|
||||||
export class SyncService {
|
export class SyncService {
|
||||||
private dirType: DirectoryType;
|
private dirType: DirectoryType;
|
||||||
@@ -50,7 +51,7 @@ export class SyncService {
|
|||||||
try {
|
try {
|
||||||
const entries = await directoryService.getEntries(force || syncConfig.overwriteExisting, test);
|
const entries = await directoryService.getEntries(force || syncConfig.overwriteExisting, test);
|
||||||
let groups = entries[0];
|
let groups = entries[0];
|
||||||
let users = entries[1];
|
let users = this.filterUnsupportedUsers(entries[1]);
|
||||||
|
|
||||||
if (groups != null && groups.length > 0) {
|
if (groups != null && groups.length > 0) {
|
||||||
this.flattenUsersToGroups(groups, groups);
|
this.flattenUsersToGroups(groups, groups);
|
||||||
@@ -102,8 +103,15 @@ export class SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
|
||||||
|
return users == null ? null : users.filter((u) => u.email == null || u.email.length <= 50);
|
||||||
|
}
|
||||||
|
|
||||||
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
||||||
let allUsers = new Set<string>();
|
let allUsers = new Set<string>();
|
||||||
|
if (allGroups == null) {
|
||||||
|
return allUsers;
|
||||||
|
}
|
||||||
for (const group of levelGroups) {
|
for (const group of levelGroups) {
|
||||||
const childGroups = allGroups.filter((g) => group.groupMemberReferenceIds.has(g.referenceId));
|
const childGroups = allGroups.filter((g) => group.groupMemberReferenceIds.has(g.referenceId));
|
||||||
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
||||||
@@ -123,6 +131,8 @@ export class SyncService {
|
|||||||
return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
case DirectoryType.Okta:
|
case DirectoryType.Okta:
|
||||||
return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
return new OneLoginDirectoryService(this.configurationService, this.logService, this.i18nService);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,9 @@ const path = require('path');
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const merge = require('webpack-merge');
|
const merge = require('webpack-merge');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
|
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
|
||||||
|
|
||||||
const extractCss = new ExtractTextPlugin({
|
|
||||||
filename: '[name].css',
|
|
||||||
disable: false,
|
|
||||||
allChunks: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -53,6 +47,7 @@ const common = {
|
|||||||
|
|
||||||
const renderer = {
|
const renderer = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
|
devtool: false,
|
||||||
target: 'electron-renderer',
|
target: 'electron-renderer',
|
||||||
node: {
|
node: {
|
||||||
__dirname: false,
|
__dirname: false,
|
||||||
@@ -93,17 +88,16 @@ const renderer = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: extractCss.extract({
|
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'css-loader',
|
loader: MiniCssExtractPlugin.loader,
|
||||||
},
|
options: {
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
publicPath: '../',
|
publicPath: '../',
|
||||||
})
|
},
|
||||||
|
},
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560
|
// Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560
|
||||||
{
|
{
|
||||||
@@ -127,11 +121,13 @@ const renderer = {
|
|||||||
chunks: ['app/vendor', 'app/main']
|
chunks: ['app/vendor', 'app/main']
|
||||||
}),
|
}),
|
||||||
new webpack.SourceMapDevToolPlugin({
|
new webpack.SourceMapDevToolPlugin({
|
||||||
filename: '[name].js.map',
|
|
||||||
include: ['app/main.js']
|
include: ['app/main.js']
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({ 'global.GENTLY': false }),
|
new webpack.DefinePlugin({ 'global.GENTLY': false }),
|
||||||
extractCss,
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].[hash].css',
|
||||||
|
chunkFilename: '[id].[hash].css',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user