mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-15 07:43:27 +00:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19e1049566 | ||
|
|
87bdc88e22 | ||
|
|
950e3ae91e | ||
|
|
a37532e1ad | ||
|
|
21ff5f311b | ||
|
|
7dd12cf0cb | ||
|
|
6f8df7a690 | ||
|
|
1ff0ef1f90 | ||
|
|
196fc10d80 | ||
|
|
7496de4cc6 | ||
|
|
02a6adf6a2 | ||
|
|
6c95575a8f | ||
|
|
39755e89a8 | ||
|
|
c5d3ca218e | ||
|
|
87a2a2a0e4 | ||
|
|
5904da2eb1 | ||
|
|
1ac0c81661 | ||
|
|
955711714d | ||
|
|
38758caac4 | ||
|
|
b41a1bdbf4 | ||
|
|
a400ab7f7d | ||
|
|
e5d0405882 | ||
|
|
f2137c02f7 | ||
|
|
4c61f465a3 | ||
|
|
6d22041eab | ||
|
|
752e26db6d | ||
|
|
094ec23f04 | ||
|
|
af8ff2901e | ||
|
|
628689c990 | ||
|
|
ab37221182 | ||
|
|
626892473f | ||
|
|
c621677852 | ||
|
|
6650b4848d | ||
|
|
c07c56f89b | ||
|
|
294590882f | ||
|
|
9151b9c2d6 | ||
|
|
5817468d09 | ||
|
|
31dd20999c | ||
|
|
4eb9c9bd4d | ||
|
|
150164534f | ||
|
|
2b2d8a9fab | ||
|
|
fb122cbbdb | ||
|
|
0b37857d29 | ||
|
|
15c1876687 | ||
|
|
473a6e391d | ||
|
|
13ad64e6f3 | ||
|
|
04e278249e | ||
|
|
e12c4ea1e2 | ||
|
|
acfa8632af | ||
|
|
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 | ||
|
|
e6a5a3c8c1 | ||
|
|
3f3590a223 | ||
|
|
c1f64d7b82 | ||
|
|
f90611c96f | ||
|
|
630e21f7c1 | ||
|
|
2e81642c0e | ||
|
|
39514b9550 | ||
|
|
2da82d5610 | ||
|
|
69f33a08b6 | ||
|
|
a05e9c7746 | ||
|
|
d8031e4f49 | ||
|
|
e6aa07ba5c | ||
|
|
173129014a | ||
|
|
8d4baa6d31 | ||
|
|
20463ce653 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ yarn-error.log
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.nupkg
|
*.nupkg
|
||||||
*.provisionprofile
|
*.provisionprofile
|
||||||
|
*.env
|
||||||
|
|||||||
2
jslib
2
jslib
Submodule jslib updated: 8375f7381a...abb54f0073
@@ -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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7818
package-lock.json
generated
7818
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
91
package.json
91
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,6 +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",
|
||||||
|
"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",
|
||||||
@@ -38,10 +41,10 @@
|
|||||||
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
||||||
"clean:dist": "rimraf ./dist/*",
|
"clean:dist": "rimraf ./dist/*",
|
||||||
"clean:dist:cli": "rimraf ./dist-cli/*",
|
"clean:dist:cli": "rimraf ./dist-cli/*",
|
||||||
"pack:lin": "npm run clean:dist && build --linux --x64 -p never",
|
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
|
||||||
"pack:mac": "npm run clean:dist && build --mac -p never",
|
"pack:mac": "npm run clean:dist && electron-builder --mac -p never",
|
||||||
"pack:win": "npm run clean:dist && build --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
"pack:win": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
"pack:win:ci": "npm run clean:dist && build --win --x64 --ia32 -p never",
|
"pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never",
|
||||||
"pack:cli": "npm run pack:cli:win | npm run pack:cli:mac | npm run pack:cli:lin",
|
"pack:cli": "npm run pack:cli:win | npm run pack:cli:mac | npm run pack:cli:lin",
|
||||||
"pack:cli:win": "pkg . --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
|
"pack:cli:win": "pkg . --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
|
||||||
"pack:cli:mac": "pkg . --targets macos-x64 --output ./dist-cli/macos/bwdc",
|
"pack:cli:mac": "pkg . --targets macos-x64 --output ./dist-cli/macos/bwdc",
|
||||||
@@ -54,20 +57,25 @@
|
|||||||
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
|
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
|
||||||
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
|
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
|
||||||
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
||||||
"publish:lin": "npm run build:dist && npm run clean:dist && build --linux --x64 -p always",
|
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
|
||||||
"publish:mac": "npm run build:dist && npm run clean:dist && build --mac -p always",
|
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
|
||||||
"publish:win": "npm run build:dist && npm run clean:dist && build --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
||||||
},
|
},
|
||||||
"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",
|
||||||
"app": "build"
|
"app": "build"
|
||||||
},
|
},
|
||||||
|
"afterSign": "scripts/notarize.js",
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"entitlements": "resources/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "resources/entitlements.mac.plist",
|
||||||
"target": [
|
"target": [
|
||||||
"dmg",
|
"dmg",
|
||||||
"zip"
|
"zip"
|
||||||
@@ -129,16 +137,16 @@
|
|||||||
"assets": "./build-cli/**/*"
|
"assets": "./build-cli/**/*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^7.2.11",
|
"@angular/compiler-cli": "^9.1.12",
|
||||||
"@microsoft/microsoft-graph-types": "^1.4.0",
|
"@microsoft/microsoft-graph-types": "^1.4.0",
|
||||||
"@ngtools/webpack": "^7.2.2",
|
"@ngtools/webpack": "^9.1.12",
|
||||||
"@types/commander": "^2.12.2",
|
"@types/commander": "^2.12.2",
|
||||||
"@types/form-data": "^2.2.1",
|
"@types/form-data": "^2.2.1",
|
||||||
"@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.17.28",
|
||||||
"@types/node-fetch": "^2.1.2",
|
"@types/node-fetch": "^2.1.2",
|
||||||
"@types/node-forge": "^0.7.5",
|
"@types/node-forge": "^0.7.5",
|
||||||
"@types/papaparse": "^4.5.3",
|
"@types/papaparse": "^4.5.3",
|
||||||
@@ -153,47 +161,46 @@
|
|||||||
"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": "3.0.14",
|
"electron": "6.1.7",
|
||||||
"electron-builder": "20.38.5",
|
"electron-builder": "22.4.0",
|
||||||
"electron-rebuild": "^1.8.2",
|
"electron-notarize": "^0.2.1",
|
||||||
"electron-reload": "^1.4.0",
|
"electron-rebuild": "^1.9.0",
|
||||||
"extract-text-webpack-plugin": "next",
|
"electron-reload": "^1.5.0",
|
||||||
|
"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",
|
||||||
"gulp-google-webfonts": "^2.0.0",
|
"gulp-google-webfonts": "^2.0.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"node-abi": "^2.5.1",
|
"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",
|
||||||
"ts-loader": "^5.3.3",
|
"ts-loader": "^7.0.5",
|
||||||
"tslint": "^5.12.1",
|
"tslint": "^5.12.1",
|
||||||
"tslint-loader": "^3.5.4",
|
"tslint-loader": "^3.5.4",
|
||||||
"typescript": "3.2.4",
|
"typescript": "3.8.3",
|
||||||
"webpack": "^4.29.0",
|
"webpack": "^4.29.0",
|
||||||
"webpack-cli": "^3.2.1",
|
"webpack-cli": "^3.2.1",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"webpack-node-externals": "^1.7.2"
|
"webpack-node-externals": "^1.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.1",
|
"@angular/animations": "9.1.12",
|
||||||
"@angular/common": "7.2.1",
|
"@angular/common": "9.1.12",
|
||||||
"@angular/compiler": "7.2.1",
|
"@angular/compiler": "9.1.12",
|
||||||
"@angular/core": "7.2.1",
|
"@angular/core": "9.1.12",
|
||||||
"@angular/forms": "7.2.1",
|
"@angular/forms": "9.1.12",
|
||||||
"@angular/http": "7.2.1",
|
"@angular/platform-browser": "9.1.12",
|
||||||
"@angular/platform-browser": "7.2.1",
|
"@angular/platform-browser-dynamic": "9.1.12",
|
||||||
"@angular/platform-browser-dynamic": "7.2.1",
|
"@angular/router": "9.1.12",
|
||||||
"@angular/router": "7.2.1",
|
"@angular/upgrade": "9.1.12",
|
||||||
"@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",
|
"angular2-toaster": "8.0.0",
|
||||||
"angular2-toaster": "6.1.0",
|
"angulartics2": "9.1.0",
|
||||||
"angulartics2": "6.3.0",
|
|
||||||
"big-integer": "1.6.36",
|
"big-integer": "1.6.36",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"chalk": "2.4.1",
|
"chalk": "2.4.1",
|
||||||
@@ -202,17 +209,21 @@
|
|||||||
"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.0.6",
|
"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": "4.0.0",
|
||||||
"inquirer": "6.2.0",
|
"inquirer": "6.2.0",
|
||||||
"keytar": "4.4.1",
|
"keytar": "4.13.0",
|
||||||
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
"ldapjs": "git+https://git@github.com/kspearrin/node-ldapjs.git",
|
||||||
"lowdb": "1.0.0",
|
"lowdb": "1.0.0",
|
||||||
"lunr": "2.3.3",
|
"lunr": "2.3.3",
|
||||||
"node-fetch": "2.2.0",
|
"node-fetch": "2.2.0",
|
||||||
"node-forge": "0.7.6",
|
"node-forge": "0.7.6",
|
||||||
"rxjs": "6.3.3",
|
"open": "7.1.0",
|
||||||
"zone.js": "0.8.28"
|
"rxjs": "6.6.2",
|
||||||
|
"tslib": "^2.0.1",
|
||||||
|
"zone.js": "0.10.3",
|
||||||
|
"zxcvbn": "4.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
resources/entitlements.mac.plist
Normal file
10
resources/entitlements.mac.plist
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
18
scripts/notarize.js
Normal file
18
scripts/notarize.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const { notarize } = require('electron-notarize');
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
|
const { electronPlatformName, appOutDir } = context;
|
||||||
|
if (electronPlatformName !== 'darwin') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
|
||||||
|
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
|
||||||
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
return await notarize({
|
||||||
|
appBundleId: 'com.bitwarden.directory-connector',
|
||||||
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
|
appleId: appleId,
|
||||||
|
appleIdPassword: appleIdPassword,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -17,6 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<h4>{{'customEnvironment' | i18n}}</h4>
|
<h4>{{'customEnvironment' | i18n}}</h4>
|
||||||
<p>{{'customEnvironmentFooter' | i18n}}</p>
|
<p>{{'customEnvironmentFooter' | i18n}}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
|
||||||
|
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl"
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
||||||
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
|
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
|
||||||
|
|||||||
@@ -18,14 +18,22 @@
|
|||||||
[(ngModel)]="masterPassword" class="form-control">
|
[(ngModel)]="masterPassword" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
<div class="d-flex">
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
<div>
|
||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
||||||
{{'logIn' | i18n}}
|
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
||||||
</button>
|
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
||||||
<button type="button" class="btn btn-link" (click)="settings()">
|
{{'logIn' | i18n}}
|
||||||
{{'settings' | i18n}}
|
</button>
|
||||||
</button>
|
<button type="button" class="btn btn-secondary ml-1" (click)="sso()">
|
||||||
|
<i class="fa fa-bank" aria-hidden="true"></i>
|
||||||
|
{{'enterpriseSingleSignOn' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
||||||
|
{{'settings' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import { Router } from '@angular/router';
|
|||||||
import { EnvironmentComponent } from './environment.component';
|
import { EnvironmentComponent } from './environment.component';
|
||||||
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
|
|
||||||
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
|
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
|
||||||
@@ -21,12 +25,18 @@ import { ModalComponent } from 'jslib/angular/components/modal.component';
|
|||||||
templateUrl: 'login.component.html',
|
templateUrl: 'login.component.html',
|
||||||
})
|
})
|
||||||
export class LoginComponent extends BaseLoginComponent {
|
export class LoginComponent extends BaseLoginComponent {
|
||||||
@ViewChild('environment', { read: ViewContainerRef }) environmentModal: ViewContainerRef;
|
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(authService: AuthService, router: Router,
|
||||||
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
storageService: StorageService, platformUtilsService: PlatformUtilsService) {
|
storageService: StorageService, stateService: StateService,
|
||||||
super(authService, router, platformUtilsService, i18nService, storageService);
|
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||||
|
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService) {
|
||||||
|
super(authService, router,
|
||||||
|
platformUtilsService, i18nService,
|
||||||
|
stateService, environmentService,
|
||||||
|
passwordGenerationService, cryptoFunctionService,
|
||||||
|
storageService);
|
||||||
super.successRoute = '/tabs/dashboard';
|
super.successRoute = '/tabs/dashboard';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +50,8 @@ export class LoginComponent extends BaseLoginComponent {
|
|||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sso() {
|
||||||
|
return super.launchSsoBrowser('connector', 'bwdc://sso-callback');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/app/accounts/sso.component.html
Normal file
30
src/app/accounts/sso.component.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<div class="container-fluid">
|
||||||
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8 col-lg-6">
|
||||||
|
<div class="card" *ngIf="!showMasterPassRedirect">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
|
{{'loading' | i18n}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" *ngIf="showMasterPassRedirect">
|
||||||
|
<h5 class="card-header">{{'setMasterPassword' | i18n}}</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-center">{{'setMasterPasswordRedirect' | i18n}}</p>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex">
|
||||||
|
<button type="button" class="btn btn-primary btn-block btn-submit"
|
||||||
|
(click)="launchWebVault()">
|
||||||
|
{{'launchWebVault' | i18n}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
||||||
|
{{'logOut' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
61
src/app/accounts/sso.component.ts
Normal file
61
src/app/accounts/sso.component.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActivatedRoute,
|
||||||
|
Router,
|
||||||
|
} from '@angular/router';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
|
|
||||||
|
import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sso',
|
||||||
|
templateUrl: 'sso.component.html',
|
||||||
|
})
|
||||||
|
export class SsoComponent extends BaseSsoComponent {
|
||||||
|
showMasterPassRedirect: boolean = false;
|
||||||
|
|
||||||
|
constructor(authService: AuthService, router: Router,
|
||||||
|
i18nService: I18nService, route: ActivatedRoute,
|
||||||
|
storageService: StorageService, stateService: StateService,
|
||||||
|
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
||||||
|
cryptoFunctionService: CryptoFunctionService,
|
||||||
|
passwordGenerationService: PasswordGenerationService, private messagingService: MessagingService,
|
||||||
|
private environmentService: EnvironmentService) {
|
||||||
|
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
|
||||||
|
apiService, cryptoFunctionService, passwordGenerationService);
|
||||||
|
this.successRoute = '/tabs/dashboard';
|
||||||
|
this.redirectUri = 'bwdc://sso-callback';
|
||||||
|
this.clientId = 'connector';
|
||||||
|
this.onSuccessfulLoginChangePasswordNavigate = this.redirectSetMasterPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
async redirectSetMasterPass() {
|
||||||
|
this.showMasterPassRedirect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchWebVault() {
|
||||||
|
const webUrl = this.environmentService.webVaultUrl == null ? 'https://vault.bitwarden.com' :
|
||||||
|
this.environmentService.webVaultUrl;
|
||||||
|
|
||||||
|
this.platformUtilsService.launchUri(webUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async logOut() {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
|
||||||
|
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
|
||||||
|
if (confirmed) {
|
||||||
|
this.messagingService.send('logout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,10 @@ import {
|
|||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { Router } from '@angular/router';
|
import {
|
||||||
|
ActivatedRoute,
|
||||||
|
Router,
|
||||||
|
} from '@angular/router';
|
||||||
|
|
||||||
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
||||||
|
|
||||||
@@ -16,6 +19,8 @@ import { AuthService } from 'jslib/abstractions/auth.service';
|
|||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
||||||
@@ -25,13 +30,19 @@ import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/comp
|
|||||||
templateUrl: 'two-factor.component.html',
|
templateUrl: 'two-factor.component.html',
|
||||||
})
|
})
|
||||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
@ViewChild('twoFactorOptions', { read: ViewContainerRef }) twoFactorOptionsModal: ViewContainerRef;
|
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(authService: AuthService, router: Router,
|
||||||
i18nService: I18nService, apiService: ApiService,
|
i18nService: I18nService, apiService: ApiService,
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver) {
|
private componentFactoryResolver: ComponentFactoryResolver, stateService: StateService,
|
||||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService);
|
storageService: StorageService, route: ActivatedRoute) {
|
||||||
|
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
||||||
|
stateService, storageService, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
await super.ngOnInit();
|
||||||
super.successRoute = '/tabs/dashboard';
|
super.successRoute = '/tabs/dashboard';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AuthGuardService } from './services/auth-guard.service';
|
|||||||
import { LaunchGuardService } from './services/launch-guard.service';
|
import { LaunchGuardService } from './services/launch-guard.service';
|
||||||
|
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { LoginComponent } from './accounts/login.component';
|
||||||
|
import { SsoComponent } from './accounts/sso.component';
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { MoreComponent } from './tabs/more.component';
|
||||||
@@ -22,6 +23,7 @@ const routes: Routes = [
|
|||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
},
|
},
|
||||||
{ path: '2fa', component: TwoFactorComponent },
|
{ path: '2fa', component: TwoFactorComponent },
|
||||||
|
{ path: 'sso', component: SsoComponent },
|
||||||
{
|
{
|
||||||
path: 'tabs',
|
path: 'tabs',
|
||||||
component: TabsComponent,
|
component: TabsComponent,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const BroadcasterSubscriptionId = 'AppComponent';
|
|||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
@ViewChild('settings', { read: ViewContainerRef }) settingsRef: ViewContainerRef;
|
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||||
|
|
||||||
toasterConfig: ToasterConfig = new ToasterConfig({
|
toasterConfig: ToasterConfig = new ToasterConfig({
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
@@ -133,6 +133,9 @@ export class AppComponent implements OnInit {
|
|||||||
properties: { label: message.label },
|
properties: { label: message.label },
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'ssoCallback':
|
||||||
|
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ 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';
|
||||||
|
|
||||||
import { EnvironmentComponent } from './accounts/environment.component';
|
import { EnvironmentComponent } from './accounts/environment.component';
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { LoginComponent } from './accounts/login.component';
|
||||||
|
import { SsoComponent } from './accounts/sso.component';
|
||||||
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
@@ -46,7 +48,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
|
Angulartics2Module.forRoot({
|
||||||
pageTracking: {
|
pageTracking: {
|
||||||
clearQueryParams: true,
|
clearQueryParams: true,
|
||||||
},
|
},
|
||||||
@@ -60,6 +62,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CalloutComponent,
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
@@ -70,6 +73,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
MoreComponent,
|
MoreComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
|
SsoComponent,
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import { ContainerService } from 'jslib/services/container.service';
|
|||||||
import { CryptoService } from 'jslib/services/crypto.service';
|
import { CryptoService } from 'jslib/services/crypto.service';
|
||||||
import { EnvironmentService } from 'jslib/services/environment.service';
|
import { EnvironmentService } from 'jslib/services/environment.service';
|
||||||
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
||||||
|
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
|
||||||
|
import { PolicyService } from 'jslib/services/policy.service';
|
||||||
import { StateService } from 'jslib/services/state.service';
|
import { StateService } from 'jslib/services/state.service';
|
||||||
import { TokenService } from 'jslib/services/token.service';
|
import { TokenService } from 'jslib/services/token.service';
|
||||||
import { UserService } from 'jslib/services/user.service';
|
import { UserService } from 'jslib/services/user.service';
|
||||||
@@ -46,7 +48,11 @@ import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstr
|
|||||||
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
||||||
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
|
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
|
||||||
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
|
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
|
||||||
|
import {
|
||||||
|
PasswordGenerationService as PasswordGenerationServiceAbstraction,
|
||||||
|
} from 'jslib/abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { PolicyService as PolicyServiceAbstraction } from 'jslib/abstractions/policy.service';
|
||||||
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
||||||
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
||||||
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
||||||
@@ -57,11 +63,12 @@ const i18nService = new I18nService(window.navigator.language, './locales');
|
|||||||
const stateService = new StateService();
|
const stateService = new StateService();
|
||||||
const broadcasterService = new BroadcasterService();
|
const broadcasterService = new BroadcasterService();
|
||||||
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
||||||
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true);
|
|
||||||
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
|
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
|
||||||
|
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, false, storageService);
|
||||||
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
||||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
||||||
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService);
|
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService,
|
||||||
|
platformUtilsService);
|
||||||
const appIdService = new AppIdService(storageService);
|
const appIdService = new AppIdService(storageService);
|
||||||
const tokenService = new TokenService(storageService);
|
const tokenService = new TokenService(storageService);
|
||||||
const apiService = new ApiService(tokenService, platformUtilsService,
|
const apiService = new ApiService(tokenService, platformUtilsService,
|
||||||
@@ -70,10 +77,12 @@ const environmentService = new EnvironmentService(apiService, storageService, nu
|
|||||||
const userService = new UserService(tokenService, storageService);
|
const userService = new UserService(tokenService, storageService);
|
||||||
const containerService = new ContainerService(cryptoService);
|
const containerService = new ContainerService(cryptoService);
|
||||||
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, null, false);
|
||||||
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
||||||
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
||||||
messagingService, i18nService);
|
messagingService, i18nService);
|
||||||
|
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, null);
|
||||||
|
const policyService = new PolicyService(userService, storageService);
|
||||||
|
|
||||||
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
|
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
|
||||||
containerService.attachToWindow(window);
|
containerService.attachToWindow(window);
|
||||||
@@ -134,6 +143,9 @@ export function initFactory(): Function {
|
|||||||
{ provide: LogServiceAbstraction, useValue: logService },
|
{ provide: LogServiceAbstraction, useValue: logService },
|
||||||
{ provide: ConfigurationService, useValue: configurationService },
|
{ provide: ConfigurationService, useValue: configurationService },
|
||||||
{ provide: SyncService, useValue: syncService },
|
{ provide: SyncService, useValue: syncService },
|
||||||
|
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
||||||
|
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
||||||
|
{ provide: PolicyServiceAbstraction, useValue: policyService },
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initFactory,
|
useFactory: initFactory,
|
||||||
|
|||||||
@@ -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,41 +33,74 @@
|
|||||||
<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">
|
||||||
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
<div class="form-radio">
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCertPath_file"
|
<input class="form-radio-input" type="radio" [value]="false" id="ssl"
|
||||||
(change)="setSslPath('sslCertPath')">
|
[(ngModel)]="ldap.startTls" name="SSL">
|
||||||
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath"
|
<label class="form-radio-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
||||||
[(ngModel)]="ldap.sslCertPath">
|
</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>
|
||||||
<div class="form-group">
|
<div class="ml-4" *ngIf="ldap.startTls">
|
||||||
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label>
|
<p>{{'ldapTlsUntrustedDesc' | i18n}}</p>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file"
|
<div class="form-group">
|
||||||
(change)="setSslPath('sslKeyPath')">
|
<label for="tlsCaPath">{{'ldapTlsCa' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath"
|
<input type="file" class="form-control-file mb-2" id="tlsCaPath_file"
|
||||||
[(ngModel)]="ldap.sslKeyPath">
|
(change)="setSslPath('tlsCaPath')">
|
||||||
|
<input type="text" class="form-control" id="tlsCaPath" name="TLSCaPath"
|
||||||
|
[(ngModel)]="ldap.tlsCaPath">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="ml-4" *ngIf="!ldap.startTls">
|
||||||
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label>
|
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCaPath_file"
|
<div class="form-group">
|
||||||
(change)="setSslPath('sslCaPath')">
|
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
<input type="file" class="form-control-file mb-2" id="sslCertPath_file"
|
||||||
[(ngModel)]="ldap.sslCaPath">
|
(change)="setSslPath('sslCertPath')">
|
||||||
|
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath"
|
||||||
|
[(ngModel)]="ldap.sslCertPath">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label>
|
||||||
|
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file"
|
||||||
|
(change)="setSslPath('sslKeyPath')">
|
||||||
|
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath"
|
||||||
|
[(ngModel)]="ldap.sslKeyPath">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label>
|
||||||
|
<input type="file" class="form-control-file mb-2" id="sslCaPath_file"
|
||||||
|
(change)="setSslPath('sslCaPath')">
|
||||||
|
<input type="text" class="form-control" id="sslCaPath" name="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>
|
||||||
@@ -218,7 +271,8 @@
|
|||||||
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small>
|
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div [hidden]="directory != directoryType.Ldap && directory != directoryType.OneLogin">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="useEmailPrefixSuffix"
|
<input class="form-check-input" type="checkbox" id="useEmailPrefixSuffix"
|
||||||
@@ -228,7 +282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.useEmailPrefixSuffix">
|
<div [hidden]="!sync.useEmailPrefixSuffix">
|
||||||
<div class="form-group" [hidden]="ldap.ad">
|
<div class="form-group" [hidden]="ldap.ad || directory != directoryType.Ldap">
|
||||||
<label for="emailPrefixAttribute">{{'emailPrefixAttribute' | i18n}}</label>
|
<label for="emailPrefixAttribute">{{'emailPrefixAttribute' | i18n}}</label>
|
||||||
<input type="text" class="form-control" id="emailPrefixAttribute"
|
<input type="text" class="form-control" id="emailPrefixAttribute"
|
||||||
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute">
|
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute">
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
src/bwdc.ts
22
src/bwdc.ts
@@ -22,9 +22,12 @@ import { LowdbStorageService } from 'jslib/services/lowdbStorage.service';
|
|||||||
import { NodeApiService } from 'jslib/services/nodeApi.service';
|
import { NodeApiService } from 'jslib/services/nodeApi.service';
|
||||||
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
import { NodeCryptoFunctionService } from 'jslib/services/nodeCryptoFunction.service';
|
||||||
import { NoopMessagingService } from 'jslib/services/noopMessaging.service';
|
import { NoopMessagingService } from 'jslib/services/noopMessaging.service';
|
||||||
|
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
|
||||||
import { TokenService } from 'jslib/services/token.service';
|
import { TokenService } from 'jslib/services/token.service';
|
||||||
import { UserService } from 'jslib/services/user.service';
|
import { UserService } from 'jslib/services/user.service';
|
||||||
|
|
||||||
|
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
||||||
|
|
||||||
import { Program } from './program';
|
import { Program } from './program';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
@@ -35,7 +38,7 @@ export class Main {
|
|||||||
logService: ConsoleLogService;
|
logService: ConsoleLogService;
|
||||||
messagingService: NoopMessagingService;
|
messagingService: NoopMessagingService;
|
||||||
storageService: LowdbStorageService;
|
storageService: LowdbStorageService;
|
||||||
secureStorageService: KeytarSecureStorageService;
|
secureStorageService: StorageServiceAbstraction;
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
platformUtilsService: CliPlatformUtilsService;
|
platformUtilsService: CliPlatformUtilsService;
|
||||||
constantsService: ConstantsService;
|
constantsService: ConstantsService;
|
||||||
@@ -50,6 +53,7 @@ export class Main {
|
|||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
configurationService: ConfigurationService;
|
configurationService: ConfigurationService;
|
||||||
syncService: SyncService;
|
syncService: SyncService;
|
||||||
|
passwordGenerationService: PasswordGenerationService;
|
||||||
program: Program;
|
program: Program;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -70,15 +74,17 @@ export class Main {
|
|||||||
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
|
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const plaintextSecrets = process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS === 'true';
|
||||||
this.i18nService = new I18nService('en', './locales');
|
this.i18nService = new I18nService('en', './locales');
|
||||||
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
|
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
|
||||||
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
|
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
|
||||||
(level) => process.env.BWCLI_DEBUG !== 'true' && level <= LogLevelType.Info);
|
(level) => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info);
|
||||||
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
this.storageService = new LowdbStorageService(null, this.dataFilePath, true);
|
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, true);
|
||||||
this.secureStorageService = new KeytarSecureStorageService(applicationName);
|
this.secureStorageService = plaintextSecrets ?
|
||||||
|
this.storageService : new KeytarSecureStorageService(applicationName);
|
||||||
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
|
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
|
||||||
this.cryptoFunctionService);
|
this.cryptoFunctionService, this.platformUtilsService);
|
||||||
this.appIdService = new AppIdService(this.storageService);
|
this.appIdService = new AppIdService(this.storageService);
|
||||||
this.tokenService = new TokenService(this.storageService);
|
this.tokenService = new TokenService(this.storageService);
|
||||||
this.messagingService = new NoopMessagingService();
|
this.messagingService = new NoopMessagingService();
|
||||||
@@ -88,10 +94,12 @@ export class Main {
|
|||||||
this.userService = new UserService(this.tokenService, this.storageService);
|
this.userService = new UserService(this.tokenService, this.storageService);
|
||||||
this.containerService = new ContainerService(this.cryptoService);
|
this.containerService = new ContainerService(this.cryptoService);
|
||||||
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
||||||
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, true);
|
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null, false);
|
||||||
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService);
|
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService,
|
||||||
|
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true');
|
||||||
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
||||||
this.apiService, this.messagingService, this.i18nService);
|
this.apiService, this.messagingService, this.i18nService);
|
||||||
|
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, null);
|
||||||
this.program = new Program(this);
|
this.program = new Program(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,9 @@
|
|||||||
"baseUrl": {
|
"baseUrl": {
|
||||||
"message": "Server URL"
|
"message": "Server URL"
|
||||||
},
|
},
|
||||||
|
"webVaultUrl": {
|
||||||
|
"message": "Web Vault Server URL"
|
||||||
|
},
|
||||||
"apiUrl": {
|
"apiUrl": {
|
||||||
"message": "API Server URL"
|
"message": "API Server URL"
|
||||||
},
|
},
|
||||||
@@ -414,8 +417,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 +444,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"
|
||||||
},
|
},
|
||||||
@@ -570,8 +588,8 @@
|
|||||||
"message": "Hide to Tray"
|
"message": "Hide to Tray"
|
||||||
},
|
},
|
||||||
"alwaysOnTop": {
|
"alwaysOnTop": {
|
||||||
"message": "Always on Top",
|
"message": "Always on Top",
|
||||||
"description": "Application window should always stay on top of other windows"
|
"description": "Application window should always stay on top of other windows"
|
||||||
},
|
},
|
||||||
"hideToMenuBar": {
|
"hideToMenuBar": {
|
||||||
"message": "Hide to Menu Bar"
|
"message": "Hide to Menu Bar"
|
||||||
@@ -587,5 +605,122 @@
|
|||||||
},
|
},
|
||||||
"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"
|
||||||
|
},
|
||||||
|
"enterpriseSingleSignOn": {
|
||||||
|
"message": "Enterprise Single Sign-On"
|
||||||
|
},
|
||||||
|
"setMasterPassword": {
|
||||||
|
"message": "Set Master Password"
|
||||||
|
},
|
||||||
|
"ssoCompleteRegistration": {
|
||||||
|
"message": "In order to complete logging in with SSO, please set a master password to access and protect your vault."
|
||||||
|
},
|
||||||
|
"newMasterPass": {
|
||||||
|
"message": "New Master Password"
|
||||||
|
},
|
||||||
|
"confirmNewMasterPass": {
|
||||||
|
"message": "Confirm New Master Password"
|
||||||
|
},
|
||||||
|
"masterPasswordPolicyInEffect": {
|
||||||
|
"message": "One or more organization policies require your master password to meet the following requirements:"
|
||||||
|
},
|
||||||
|
"policyInEffectMinComplexity": {
|
||||||
|
"message": "Minimum complexity score of $SCORE$",
|
||||||
|
"placeholders": {
|
||||||
|
"score": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policyInEffectMinLength": {
|
||||||
|
"message": "Minimum length of $LENGTH$",
|
||||||
|
"placeholders": {
|
||||||
|
"length": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policyInEffectUppercase": {
|
||||||
|
"message": "Contain one or more uppercase characters"
|
||||||
|
},
|
||||||
|
"policyInEffectLowercase": {
|
||||||
|
"message": "Contain one or more lowercase characters"
|
||||||
|
},
|
||||||
|
"policyInEffectNumbers": {
|
||||||
|
"message": "Contain one or more numbers"
|
||||||
|
},
|
||||||
|
"policyInEffectSpecial": {
|
||||||
|
"message": "Contain one or more of the following special characters $CHARS$",
|
||||||
|
"placeholders": {
|
||||||
|
"chars": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "!@#$%^&*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"masterPassDesc": {
|
||||||
|
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
|
||||||
|
},
|
||||||
|
"reTypeMasterPass": {
|
||||||
|
"message": "Re-type Master Password"
|
||||||
|
},
|
||||||
|
"masterPassHint": {
|
||||||
|
"message": "Master Password Hint (optional)"
|
||||||
|
},
|
||||||
|
"masterPassHintDesc": {
|
||||||
|
"message": "A master password hint can help you remember your password if you forget it."
|
||||||
|
},
|
||||||
|
"strong": {
|
||||||
|
"message": "Strong",
|
||||||
|
"description": "ex. A strong password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"good": {
|
||||||
|
"message": "Good",
|
||||||
|
"description": "ex. A good password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"weak": {
|
||||||
|
"message": "Weak",
|
||||||
|
"description": "ex. A weak password. Scale: Weak -> Good -> Strong"
|
||||||
|
},
|
||||||
|
"weakMasterPassword": {
|
||||||
|
"message": "Weak Master Password"
|
||||||
|
},
|
||||||
|
"weakMasterPasswordDesc": {
|
||||||
|
"message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?"
|
||||||
|
},
|
||||||
|
"errorOccurred": {
|
||||||
|
"message": "An error has occurred."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Error"
|
||||||
|
},
|
||||||
|
"masterPassLength": {
|
||||||
|
"message": "Master password must be at least 8 characters long."
|
||||||
|
},
|
||||||
|
"masterPassDoesntMatch": {
|
||||||
|
"message": "Master password confirmation does not match."
|
||||||
|
},
|
||||||
|
"masterPasswordPolicyRequirementsNotMet": {
|
||||||
|
"message": "Your new master password does not meet the policy requirements."
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"message": "Loading"
|
||||||
|
},
|
||||||
|
"setMasterPasswordRedirect": {
|
||||||
|
"message": "In order to log in with SSO from the Directory Connector, you must first log in through the web vault to set your master password."
|
||||||
|
},
|
||||||
|
"launchWebVault": {
|
||||||
|
"message": "Launch Web Vault"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/main.ts
24
src/main.ts
@@ -52,7 +52,8 @@ export class Main {
|
|||||||
this.i18nService = new I18nService('en', './locales/');
|
this.i18nService = new I18nService('en', './locales/');
|
||||||
this.storageService = new ElectronStorageService(app.getPath('userData'));
|
this.storageService = new ElectronStorageService(app.getPath('userData'));
|
||||||
|
|
||||||
this.windowMain = new WindowMain(this.storageService, false, 800, 600);
|
this.windowMain = new WindowMain(this.storageService, false, 800, 600,
|
||||||
|
(arg) => this.processDeepLink(arg));
|
||||||
this.menuMain = new MenuMain(this);
|
this.menuMain = new MenuMain(this);
|
||||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
|
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
|
||||||
this.messagingService.send('checkingForUpdate');
|
this.messagingService.send('checkingForUpdate');
|
||||||
@@ -78,11 +79,32 @@ export class Main {
|
|||||||
this.messagingMain.init();
|
this.messagingMain.init();
|
||||||
await this.updaterMain.init();
|
await this.updaterMain.init();
|
||||||
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
|
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
|
||||||
|
|
||||||
|
if (!app.isDefaultProtocolClient('bwdc')) {
|
||||||
|
app.setAsDefaultProtocolClient('bwdc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process protocol for macOS
|
||||||
|
app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.processDeepLink([url]);
|
||||||
|
});
|
||||||
}, (e: any) => {
|
}, (e: any) => {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private processDeepLink(argv: string[]): void {
|
||||||
|
argv.filter((s) => s.indexOf('bwdc://') === 0).forEach((s) => {
|
||||||
|
const url = new URL(s);
|
||||||
|
const code = url.searchParams.get('code');
|
||||||
|
const receivedState = url.searchParams.get('state');
|
||||||
|
if (code != null && receivedState != null) {
|
||||||
|
this.messagingService.send('ssoCallback', { code: code, state: receivedState });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const main = new Main();
|
const main = new Main();
|
||||||
|
|||||||
@@ -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.1",
|
"version": "2.8.2",
|
||||||
"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.0.6",
|
"electron-updater": "4.2.0",
|
||||||
"keytar": "4.4.1"
|
"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);
|
||||||
@@ -81,6 +86,7 @@ export class Program extends BaseProgram {
|
|||||||
.description('Log into a user account.')
|
.description('Log into a user account.')
|
||||||
.option('--method <method>', 'Two-step login method.')
|
.option('--method <method>', 'Two-step login method.')
|
||||||
.option('--code <code>', 'Two-step login code.')
|
.option('--code <code>', 'Two-step login code.')
|
||||||
|
.option('--sso', 'Log in with Single-Sign On.')
|
||||||
.on('--help', () => {
|
.on('--help', () => {
|
||||||
writeLn('\n Notes:');
|
writeLn('\n Notes:');
|
||||||
writeLn('');
|
writeLn('');
|
||||||
@@ -91,11 +97,14 @@ export class Program extends BaseProgram {
|
|||||||
writeLn(' bw login');
|
writeLn(' bw login');
|
||||||
writeLn(' bw login john@example.com myPassword321');
|
writeLn(' bw login john@example.com myPassword321');
|
||||||
writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213');
|
writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213');
|
||||||
|
writeLn(' bw login --sso');
|
||||||
writeLn('', true);
|
writeLn('', true);
|
||||||
})
|
})
|
||||||
.action(async (email: string, password: string, cmd: program.Command) => {
|
.action(async (email: string, password: string, cmd: program.Command) => {
|
||||||
await this.exitIfAuthed();
|
await this.exitIfAuthed();
|
||||||
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService);
|
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
|
||||||
|
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
|
||||||
|
this.main.platformUtilsService, 'connector');
|
||||||
const response = await command.run(email, password, cmd);
|
const response = await command.run(email, password, cmd);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
@@ -184,6 +193,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 +204,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) => {
|
||||||
|
|||||||
11
src/scss/bootstrap.scss
vendored
11
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, "secondary": #ced4da, "secondary-alt": #1A3B66);
|
||||||
$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;
|
||||||
@@ -8,4 +8,13 @@ $h4-font-size: 1rem;
|
|||||||
$h5-font-size: 1rem;
|
$h5-font-size: 1rem;
|
||||||
$h6-font-size: 1rem;
|
$h6-font-size: 1rem;
|
||||||
|
|
||||||
|
$primary: map_get($theme-colors, 'primary');
|
||||||
|
$primary-accent: map_get($theme-colors, 'primary-accent');
|
||||||
|
$success: map_get($theme-colors, 'success');
|
||||||
|
$info: map_get($theme-colors, 'info');
|
||||||
|
$warning: map_get($theme-colors, 'warning');
|
||||||
|
$danger: map_get($theme-colors, 'danger');
|
||||||
|
$secondary: map_get($theme-colors, 'secondary');
|
||||||
|
$secondary-alt: map_get($theme-colors, 'secondary-alt');
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap.scss";
|
@import "~bootstrap/scss/bootstrap.scss";
|
||||||
|
|||||||
@@ -53,3 +53,68 @@ ul.testing-list {
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border-color: #ddd;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.callout-heading {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-primary {
|
||||||
|
border-left-color: $primary;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-info {
|
||||||
|
border-left-color: $info;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-danger {
|
||||||
|
border-left-color: $danger;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-success {
|
||||||
|
border-left-color: $success;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-warning {
|
||||||
|
border-left-color: $warning;
|
||||||
|
|
||||||
|
.callout-heading {
|
||||||
|
color: $warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 40px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -69,7 +70,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements Direc
|
|||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(setFilter);
|
groups = await this.getGroups(setFilter);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -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,16 +306,24 @@ 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();
|
||||||
const members: any = memRes.value;
|
while (true) {
|
||||||
if (members != null) {
|
const members: any = memRes.value;
|
||||||
for (const member of members) {
|
if (members != null) {
|
||||||
if (member[ObjectType] === '#microsoft.graph.group') {
|
for (const member of members) {
|
||||||
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
if (member[ObjectType] === '#microsoft.graph.group') {
|
||||||
} else if (member[ObjectType] === '#microsoft.graph.user') {
|
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
||||||
entry.userMemberExternalIds.add((member as graphType.User).id);
|
} else if (member[ObjectType] === '#microsoft.graph.user') {
|
||||||
|
entry.userMemberExternalIds.add((member as graphType.User).id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (memRes[NextLink] == null) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const nextMemReq = this.client.api(memRes[NextLink]);
|
||||||
|
memRes = await nextMemReq.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { GroupEntry } from '../models/groupEntry';
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
@@ -66,13 +68,16 @@ export abstract class BaseDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
|
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
|
||||||
setFilter: [boolean, Set<string>]): UserEntry[] {
|
setFilter: [boolean, Set<string>], syncConfig: SyncConfiguration): UserEntry[] {
|
||||||
if (setFilter == null || users == null) {
|
if (setFilter == null || users == null) {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
return users.filter((u) => {
|
return users.filter((u) => {
|
||||||
if (u.disabled || u.deleted) {
|
if (u.deleted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (u.disabled && syncConfig.removeDisabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,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';
|
||||||
|
|
||||||
const StoredSecurely = '[STORED SECURELY]';
|
const StoredSecurely = '[STORED SECURELY]';
|
||||||
@@ -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',
|
||||||
@@ -25,7 +27,8 @@ const Keys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ConfigurationService {
|
export class ConfigurationService {
|
||||||
constructor(private storageService: StorageService, private secureStorageService: StorageService) { }
|
constructor(private storageService: StorageService, private secureStorageService: StorageService,
|
||||||
|
private useSecureStorageForSecrets = true) { }
|
||||||
|
|
||||||
async getDirectory<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);
|
||||||
@@ -33,61 +36,77 @@ export class ConfigurationService {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
if (this.useSecureStorageForSecrets) {
|
||||||
case DirectoryType.Ldap:
|
switch (type) {
|
||||||
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
|
case DirectoryType.Ldap:
|
||||||
break;
|
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
|
||||||
case DirectoryType.AzureActiveDirectory:
|
break;
|
||||||
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
|
case DirectoryType.AzureActiveDirectory:
|
||||||
break;
|
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
|
||||||
case DirectoryType.Okta:
|
break;
|
||||||
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
|
case DirectoryType.Okta:
|
||||||
break;
|
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
|
||||||
case DirectoryType.GSuite:
|
break;
|
||||||
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
case DirectoryType.GSuite:
|
||||||
break;
|
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
||||||
|
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);
|
||||||
switch (type) {
|
if (this.useSecureStorageForSecrets) {
|
||||||
case DirectoryType.Ldap:
|
switch (type) {
|
||||||
if (savedConfig.password == null) {
|
case DirectoryType.Ldap:
|
||||||
await this.secureStorageService.remove(Keys.ldap);
|
if (savedConfig.password == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.ldap);
|
||||||
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
|
} else {
|
||||||
savedConfig.password = StoredSecurely;
|
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
|
||||||
}
|
savedConfig.password = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.AzureActiveDirectory:
|
break;
|
||||||
if (savedConfig.key == null) {
|
case DirectoryType.AzureActiveDirectory:
|
||||||
await this.secureStorageService.remove(Keys.azure);
|
if (savedConfig.key == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.azure);
|
||||||
await this.secureStorageService.save(Keys.azure, savedConfig.key);
|
} else {
|
||||||
savedConfig.key = StoredSecurely;
|
await this.secureStorageService.save(Keys.azure, savedConfig.key);
|
||||||
}
|
savedConfig.key = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.Okta:
|
break;
|
||||||
if (savedConfig.token == null) {
|
case DirectoryType.Okta:
|
||||||
await this.secureStorageService.remove(Keys.okta);
|
if (savedConfig.token == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.okta);
|
||||||
await this.secureStorageService.save(Keys.okta, savedConfig.token);
|
} else {
|
||||||
savedConfig.token = StoredSecurely;
|
await this.secureStorageService.save(Keys.okta, savedConfig.token);
|
||||||
}
|
savedConfig.token = StoredSecurely;
|
||||||
break;
|
}
|
||||||
case DirectoryType.GSuite:
|
break;
|
||||||
if (savedConfig.privateKey == null) {
|
case DirectoryType.GSuite:
|
||||||
await this.secureStorageService.remove(Keys.gsuite);
|
if (savedConfig.privateKey == null) {
|
||||||
} else {
|
await this.secureStorageService.remove(Keys.gsuite);
|
||||||
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
} else {
|
||||||
savedConfig.privateKey.replace(/\\n/g, '\n');
|
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
||||||
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
|
savedConfig.privateKey.replace(/\\n/g, '\n');
|
||||||
savedConfig.privateKey = StoredSecurely;
|
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
|
||||||
}
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
|
|
||||||
await this.auth();
|
await this.auth();
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[] = [];
|
||||||
if (this.syncConfig.users) {
|
if (this.syncConfig.users) {
|
||||||
users = await this.getUsers();
|
users = await this.getUsers();
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
let groups: GroupEntry[];
|
let groups: GroupEntry[];
|
||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(setFilter);
|
groups = await this.getGroups(setFilter, users);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -140,7 +140,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
private async getGroups(setFilter: [boolean, Set<string>], users: UserEntry[]): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
if (res.data.groups != null) {
|
if (res.data.groups != null) {
|
||||||
for (const group of res.data.groups) {
|
for (const group of res.data.groups) {
|
||||||
if (!this.filterOutResult(setFilter, group.name)) {
|
if (!this.filterOutResult(setFilter, group.name)) {
|
||||||
const entry = await this.buildGroup(group);
|
const entry = await this.buildGroup(group, users);
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildGroup(group: admin_directory_v1.Schema$Group) {
|
private async buildGroup(group: admin_directory_v1.Schema$Group, users: UserEntry[]) {
|
||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
const entry = new GroupEntry();
|
const entry = new GroupEntry();
|
||||||
@@ -192,18 +192,18 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements Dire
|
|||||||
if (member.type == null) {
|
if (member.type == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (member.role == null || member.role.toLowerCase() !== 'member') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (member.status == null || member.status.toLowerCase() !== 'active') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = member.type.toLowerCase();
|
const type = member.type.toLowerCase();
|
||||||
if (type === 'user') {
|
if (type === 'user') {
|
||||||
|
if (member.status == null || member.status.toLowerCase() !== 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
entry.userMemberExternalIds.add(member.id);
|
entry.userMemberExternalIds.add(member.id);
|
||||||
} else if (type === 'group') {
|
} else if (type === 'group') {
|
||||||
entry.groupMemberReferenceIds.add(member.id);
|
entry.groupMemberReferenceIds.add(member.id);
|
||||||
|
} else if (type === 'customer') {
|
||||||
|
for (const user of users) {
|
||||||
|
entry.userMemberExternalIds.add(user.externalId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private makeSearchPath(pathPrefix: string) {
|
private makeSearchPath(pathPrefix: string) {
|
||||||
|
if (this.dirConfig.rootPath.toLowerCase().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,34 +327,43 @@ 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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tlsOptions: any = {};
|
||||||
if (this.dirConfig.ssl) {
|
if (this.dirConfig.ssl) {
|
||||||
const tlsOptions: any = {};
|
if (this.dirConfig.sslAllowUnauthorized) {
|
||||||
if (this.dirConfig.sslAllowUnauthorized != null) {
|
|
||||||
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
||||||
}
|
}
|
||||||
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
if (!this.dirConfig.startTls) {
|
||||||
fs.existsSync(this.dirConfig.sslCaPath)) {
|
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
||||||
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
fs.existsSync(this.dirConfig.sslCaPath)) {
|
||||||
}
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
||||||
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' &&
|
}
|
||||||
fs.existsSync(this.dirConfig.sslCertPath)) {
|
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' &&
|
||||||
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
fs.existsSync(this.dirConfig.sslCertPath)) {
|
||||||
}
|
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
||||||
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' &&
|
}
|
||||||
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' &&
|
||||||
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
||||||
}
|
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
||||||
if (Object.keys(tlsOptions).length > 0) {
|
}
|
||||||
options.tlsOptions = tlsOptions;
|
} 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) {
|
||||||
|
options.tlsOptions = tlsOptions;
|
||||||
|
}
|
||||||
|
|
||||||
this.client = ldap.createClient(options);
|
this.client = ldap.createClient(options);
|
||||||
|
|
||||||
const user = this.dirConfig.username == null || this.dirConfig.username.trim() === '' ? null :
|
const user = this.dirConfig.username == null || this.dirConfig.username.trim() === '' ? null :
|
||||||
@@ -364,13 +376,29 @@ export class LdapDirectoryService implements DirectoryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client.bind(user, pass, (err) => {
|
if (this.dirConfig.startTls && this.dirConfig.ssl) {
|
||||||
if (err != null) {
|
this.client.starttls(options.tlsOptions, undefined, (err, res) => {
|
||||||
reject(err.message);
|
if (err != null) {
|
||||||
} else {
|
reject(err.message);
|
||||||
resolve();
|
} else {
|
||||||
}
|
this.client.bind(user, pass, (err2) => {
|
||||||
});
|
if (err2 != null) {
|
||||||
|
reject(err2.message);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.client.bind(user, pass, (err) => {
|
||||||
|
if (err != null) {
|
||||||
|
reject(err.message);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ import { DirectoryService } from './directory.service';
|
|||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { LogService } from 'jslib/abstractions/log.service';
|
import { LogService } from 'jslib/abstractions/log.service';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
import * as https from 'https';
|
||||||
const okta = require('@okta/okta-sdk-nodejs');
|
|
||||||
|
|
||||||
export class OktaDirectoryService extends BaseDirectoryService implements DirectoryService {
|
export class OktaDirectoryService extends BaseDirectoryService implements DirectoryService {
|
||||||
private dirConfig: OktaConfiguration;
|
private dirConfig: OktaConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
private client: any;
|
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
||||||
private i18nService: I18nService) {
|
private i18nService: I18nService) {
|
||||||
@@ -45,11 +43,6 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new okta.Client({
|
|
||||||
orgUrl: this.dirConfig.orgUrl,
|
|
||||||
token: this.dirConfig.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[];
|
||||||
if (this.syncConfig.users) {
|
if (this.syncConfig.users) {
|
||||||
users = await this.getUsers(force);
|
users = await this.getUsers(force);
|
||||||
@@ -59,7 +52,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.groupFilter);
|
||||||
groups = await this.getGroups(this.forceGroup(force, users), setFilter);
|
groups = await this.getGroups(this.forceGroup(force, users), setFilter);
|
||||||
users = this.filterUsersFromGroupsSet(users, groups, setFilter);
|
users = this.filterUsersFromGroupsSet(users, groups, setFilter, this.syncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
@@ -72,12 +65,15 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
|
||||||
this.logService.info('Querying users.');
|
this.logService.info('Querying users.');
|
||||||
const usersPromise = this.client.listUsers({ filter: oktaFilter }).each((user: any) => {
|
const usersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(oktaFilter))
|
||||||
const entry = this.buildUser(user);
|
.then((users: any[]) => {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
for (const user of users) {
|
||||||
entries.push(entry);
|
const entry = this.buildUser(user);
|
||||||
}
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
});
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
||||||
let deactUsersPromise: any;
|
let deactUsersPromise: any;
|
||||||
@@ -86,12 +82,15 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
if (oktaFilter != null) {
|
if (oktaFilter != null) {
|
||||||
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter;
|
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter;
|
||||||
}
|
}
|
||||||
deactUsersPromise = this.client.listUsers({ filter: deactOktaFilter }).each((user: any) => {
|
deactUsersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(deactOktaFilter))
|
||||||
const entry = this.buildUser(user);
|
.then((users: any[]) => {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
for (const user of users) {
|
||||||
entries.push(entry);
|
const entry = this.buildUser(user);
|
||||||
}
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
});
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
deactUsersPromise = Promise.resolve();
|
deactUsersPromise = Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -116,10 +115,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
||||||
|
|
||||||
this.logService.info('Querying groups.');
|
this.logService.info('Querying groups.');
|
||||||
await this.client.listGroups({ filter: oktaFilter }).each(async (group: any) => {
|
await this.apiGetMany('groups?filter=' + this.encodeUrlParameter(oktaFilter)).then(async (groups: any[]) => {
|
||||||
const entry = await this.buildGroup(group);
|
for (const group of groups) {
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
const entry = await this.buildGroup(group);
|
||||||
entries.push(entry);
|
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
// throttle some to avoid rate limiting
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return entries;
|
return entries;
|
||||||
@@ -131,8 +134,10 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
entry.referenceId = group.id;
|
entry.referenceId = group.id;
|
||||||
entry.name = group.profile.name;
|
entry.name = group.profile.name;
|
||||||
|
|
||||||
await this.client.listGroupUsers(group.id).each((user: any) => {
|
await this.apiGetMany('groups/' + group.id + '/users').then((users: any[]) => {
|
||||||
entry.userMemberExternalIds.add(user.id);
|
for (const user of users) {
|
||||||
|
entry.userMemberExternalIds.add(user.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -152,4 +157,88 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct
|
|||||||
|
|
||||||
return '(' + baseFilter + ') and ' + updatedFilter;
|
return '(' + baseFilter + ') and ' + updatedFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private encodeUrlParameter(filter: string): string {
|
||||||
|
return filter == null ? '' : encodeURIComponent(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
|
||||||
|
const u = new URL(url);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
https.get({
|
||||||
|
hostname: u.hostname,
|
||||||
|
path: u.pathname + u.search,
|
||||||
|
port: 443,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'SSWS ' + this.dirConfig.token,
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
}, (res) => {
|
||||||
|
let body = '';
|
||||||
|
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseJson = JSON.parse(body);
|
||||||
|
if (res.headers != null) {
|
||||||
|
const headersMap = new Map<string, string | string[]>();
|
||||||
|
for (const key in res.headers) {
|
||||||
|
if (res.headers.hasOwnProperty(key)) {
|
||||||
|
const val = res.headers[key];
|
||||||
|
headersMap.set(key.toLowerCase(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve([responseJson, headersMap]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve([responseJson, null]);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('error', () => {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
|
const url = endpoint.indexOf('https://') === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`;
|
||||||
|
const response = await this.apiGetCall(url);
|
||||||
|
if (response == null || response[0] == null || !Array.isArray(response[0])) {
|
||||||
|
throw new Error('API call failed.');
|
||||||
|
}
|
||||||
|
if (response[0].length === 0) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
currentData = currentData.concat(response[0]);
|
||||||
|
if (response[1] == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
const linkHeader = response[1].get('link');
|
||||||
|
if (linkHeader == null || Array.isArray(linkHeader)) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
let nextLink: string = null;
|
||||||
|
const linkHeaderParts = linkHeader.split(',');
|
||||||
|
for (const part of linkHeaderParts) {
|
||||||
|
if (part.indexOf('; rel="next"') > -1) {
|
||||||
|
const subParts = part.split(';');
|
||||||
|
if (subParts.length > 0 && subParts[0].indexOf('https://') > -1) {
|
||||||
|
nextLink = subParts[0].replace('>', '').replace('<', '').trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextLink == null) {
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
return this.apiGetMany(nextLink, currentData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
195
src/services/onelogin-directory.service.ts
Normal file
195
src/services/onelogin-directory.service.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
// Basic email validation: something@something.something
|
||||||
|
const ValidEmailRegex = /^\S+@\S+\.\S+$/;
|
||||||
|
|
||||||
|
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, this.syncConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.deleted = false;
|
||||||
|
entry.disabled = user.status === 2;
|
||||||
|
entry.email = user.email;
|
||||||
|
if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== '') {
|
||||||
|
if (this.validEmailAddress(user.username)) {
|
||||||
|
entry.email = user.username;
|
||||||
|
} else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) {
|
||||||
|
entry.email = user.username + this.syncConfig.emailSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.email != null) {
|
||||||
|
entry.email = entry.email.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
if (!this.validEmailAddress(entry.email)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validEmailAddress(email: string) {
|
||||||
|
return email != null && email !== '' && ValidEmailRegex.test(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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: MiniCssExtractPlugin.loader,
|
||||||
loader: 'css-loader',
|
options: {
|
||||||
|
publicPath: '../',
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
loader: 'sass-loader',
|
'css-loader',
|
||||||
},
|
'sass-loader',
|
||||||
],
|
],
|
||||||
publicPath: '../',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
// 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