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

Apply Prettier (#194)

This commit is contained in:
Oscar Hinton
2021-12-20 17:14:18 +01:00
committed by GitHub
parent 225073aa33
commit 096196fcd5
76 changed files with 6056 additions and 5208 deletions

View File

@@ -1,4 +1,5 @@
## Type of change ## Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature development - [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@@ -6,27 +7,26 @@
- [ ] Other - [ ] Other
## Objective ## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes ## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories--> <!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why - **file.ext:** Description of what was changed and why
## Screenshots ## Screenshots
<!--Required for any UI changes. Delete if not applicable--> <!--Required for any UI changes. Delete if not applicable-->
## Testing requirements ## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing--> <!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required) - [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)

14
.vscode/launch.json vendored
View File

@@ -7,10 +7,7 @@
"name": "Electron: Main", "name": "Electron: Main",
"protocol": "inspector", "protocol": "inspector",
"cwd": "${workspaceRoot}/build", "cwd": "${workspaceRoot}/build",
"runtimeArgs": [ "runtimeArgs": ["--remote-debugging-port=9223", "."],
"--remote-debugging-port=9223",
"."
],
"windows": { "windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}, },
@@ -31,18 +28,13 @@
"protocol": "inspector", "protocol": "inspector",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/build-cli/bwdc.js", "program": "${workspaceFolder}/build-cli/bwdc.js",
"args": [ "args": ["sync"]
"sync"
]
} }
], ],
"compounds": [ "compounds": [
{ {
"name": "Electron: All", "name": "Electron: All",
"configurations": [ "configurations": ["Electron: Main", "Electron: Renderer"]
"Electron: Main",
"Electron: Renderer"
]
} }
] ]
} }

View File

@@ -6,6 +6,7 @@
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups. The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
Supported directories: Supported directories:
- Active Directory - Active Directory
- Any other LDAP-based directory - Any other LDAP-based directory
- Azure Active Directory - Azure Active Directory

View File

@@ -1,16 +1,16 @@
require('dotenv').config(); require("dotenv").config();
const { notarize } = require('electron-notarize'); const { notarize } = require("electron-notarize");
exports.default = async function notarizing(context) { exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context; const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') { if (electronPlatformName !== "darwin") {
return; return;
} }
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID; const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`; const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
return await notarize({ return await notarize({
appBundleId: 'com.bitwarden.directory-connector', appBundleId: "com.bitwarden.directory-connector",
appPath: `${appOutDir}/${appName}.app`, appPath: `${appOutDir}/${appName}.app`,
appleId: appleId, appleId: appleId,
appleIdPassword: appleIdPassword, appleIdPassword: appleIdPassword,

View File

@@ -1,9 +1,6 @@
exports.default = async function (configuration) { exports.default = async function (configuration) {
if ( if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && console.log(`[*] Signing file: ${configuration.path}`);
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
require("child_process").execSync( require("child_process").execSync(
`azuresigntool sign ` + `azuresigntool sign ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` + `-kvu ${process.env.SIGNING_VAULT_URL} ` +
@@ -16,7 +13,7 @@ exports.default = async function(configuration) {
`-tr http://timestamp.digicert.com ` + `-tr http://timestamp.digicert.com ` +
`"${configuration.path}"`, `"${configuration.path}"`,
{ {
stdio: "inherit" stdio: "inherit",
} }
); );
} }

View File

@@ -2,25 +2,38 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8 col-lg-6"> <div class="col-md-8 col-lg-6">
<p class="text-center font-weight-bold">{{'welcome' | i18n}}</p> <p class="text-center font-weight-bold">{{ "welcome" | i18n }}</p>
<p class="text-center">{{'logInDesc' | i18n}}</p> <p class="text-center">{{ "logInDesc" | i18n }}</p>
<div class="card"> <div class="card">
<h5 class="card-header">{{'logIn' | i18n}}</h5> <h5 class="card-header">{{ "logIn" | i18n }}</h5>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="client_id">{{'clientId' | i18n}}</label> <label for="client_id">{{ "clientId" | i18n }}</label>
<input id="client_id" name="ClientId" [(ngModel)]="clientId" <input id="client_id" name="ClientId" [(ngModel)]="clientId" class="form-control" />
class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row-main"> <div class="row-main">
<label for="client_secret">{{'clientSecret' | i18n}}</label> <label for="client_secret">{{ "clientSecret" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input type="{{showSecret ? 'text' : 'password'}}" id="client_secret" name="ClientSecret" <input
[(ngModel)]="clientSecret" class="form-control"> type="{{ showSecret ? 'text' : 'password' }}"
id="client_secret"
name="ClientSecret"
[(ngModel)]="clientSecret"
class="form-control"
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleSecret()"> <button
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showSecret ? 'fa-eye-slash' : 'fa-eye'"></i> type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleSecret()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="showSecret ? 'fa-eye-slash' : 'fa-eye'"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -31,11 +44,11 @@
<button type="submit" class="btn btn-primary" [disabled]="form.loading"> <button type="submit" class="btn btn-primary" [disabled]="form.loading">
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i> <i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i> <i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
{{'logIn' | i18n}} {{ "logIn" | i18n }}
</button> </button>
</div> </div>
<button type="button" class="btn btn-link ml-auto" (click)="settings()"> <button type="button" class="btn btn-link ml-auto" (click)="settings()">
{{'settings' | i18n}} {{ "settings" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -4,61 +4,81 @@ import {
Input, Input,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { EnvironmentComponent } from './environment.component'; import { EnvironmentComponent } from "./environment.component";
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from "../../services/configuration.service";
@Component({ @Component({
selector: 'app-apiKey', selector: "app-apiKey",
templateUrl: 'apiKey.component.html', templateUrl: "apiKey.component.html",
}) })
export class ApiKeyComponent { export class ApiKeyComponent {
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef; @ViewChild("environment", { read: ViewContainerRef, static: true })
@Input() clientId: string = ''; environmentModal: ViewContainerRef;
@Input() clientSecret: string = ''; @Input() clientId: string = "";
@Input() clientSecret: string = "";
formPromise: Promise<any>; formPromise: Promise<any>;
successRoute = '/tabs/dashboard'; successRoute = "/tabs/dashboard";
showSecret: boolean = false; showSecret: boolean = false;
constructor(private authService: AuthService, private apiKeyService: ApiKeyService, private router: Router, constructor(
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, private authService: AuthService,
private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService, private apiKeyService: ApiKeyService,
private modalService: ModalService, private logService: LogService) { } private router: Router,
private i18nService: I18nService,
private componentFactoryResolver: ComponentFactoryResolver,
private configurationService: ConfigurationService,
private platformUtilsService: PlatformUtilsService,
private modalService: ModalService,
private logService: LogService
) {}
async submit() { async submit() {
if (this.clientId == null || this.clientId === '') { if (this.clientId == null || this.clientId === "") {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('clientIdRequired')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("clientIdRequired")
);
return; return;
} }
if (!this.clientId.startsWith('organization')) { if (!this.clientId.startsWith("organization")) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('orgApiKeyRequired')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("orgApiKeyRequired")
);
return; return;
} }
if (this.clientSecret == null || this.clientSecret === '') { if (this.clientSecret == null || this.clientSecret === "") {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('clientSecretRequired')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("clientSecretRequired")
);
return; return;
} }
const idParts = this.clientId.split('.'); const idParts = this.clientId.split(".");
if (idParts.length !== 2 || idParts[0] !== 'organization' || !Utils.isGuid(idParts[1])) { if (idParts.length !== 2 || idParts[0] !== "organization" || !Utils.isGuid(idParts[1])) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('invalidClientId')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidClientId")
);
return; return;
} }
@@ -74,7 +94,10 @@ export class ApiKeyComponent {
} }
async settings() { async settings() {
const [modalRef, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal); const [modalRef, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
childComponent.onSaved.subscribe(() => { childComponent.onSaved.subscribe(() => {
modalRef.close(); modalRef.close();
@@ -82,6 +105,6 @@ export class ApiKeyComponent {
} }
toggleSecret() { toggleSecret() {
this.showSecret = !this.showSecret; this.showSecret = !this.showSecret;
document.getElementById('client_secret').focus(); document.getElementById("client_secret").focus();
} }
} }

View File

@@ -2,40 +2,58 @@
<div class="modal-dialog"> <div class="modal-dialog">
<form class="modal-content" (ngSubmit)="submit()"> <form class="modal-content" (ngSubmit)="submit()">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">{{'settings' | i18n}}</h3> <h3 class="modal-title">{{ "settings" | i18n }}</h3>
<button type="button" class="close" data-dismiss="modal" title="Close"> <button type="button" class="close" data-dismiss="modal" title="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h4>{{'selfHostedEnvironment' | i18n}}</h4> <h4>{{ "selfHostedEnvironment" | i18n }}</h4>
<p>{{'selfHostedEnvironmentFooter' | i18n}}</p> <p>{{ "selfHostedEnvironmentFooter" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="baseUrl">{{'baseUrl' | i18n}}</label> <label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" class="form-control"> <input
<small class="text-muted form-text">{{'ex' | i18n}} https://bitwarden.company.com</small> id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
class="form-control"
/>
<small class="text-muted form-text"
>{{ "ex" | i18n }} https://bitwarden.company.com</small
>
</div> </div>
<h4>{{'customEnvironment' | i18n}}</h4> <h4>{{ "customEnvironment" | i18n }}</h4>
<p>{{'customEnvironmentFooter' | i18n}}</p> <p>{{ "customEnvironmentFooter" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label> <label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl" <input
class="form-control"> id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
class="form-control"
/>
</div> </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" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="identityUrl">{{'identityUrl' | i18n}}</label> <label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl" <input
class="form-control"> id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
class="form-control"
/>
</div> </div>
</div> </div>
<div class="modal-footer justify-content-start"> <div class="modal-footer justify-content-start">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="fa fa-save fa-fw"></i> <i class="fa fa-save fa-fw"></i>
{{'save' | i18n}} {{ "save" | i18n }}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,18 +1,21 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component'; import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
@Component({ @Component({
selector: 'app-environment', selector: "app-environment",
templateUrl: 'environment.component.html', templateUrl: "environment.component.html",
}) })
export class EnvironmentComponent extends BaseEnvironmentComponent { export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(environmentService: EnvironmentService, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService) { environmentService: EnvironmentService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(platformUtilsService, environmentService, i18nService); super(platformUtilsService, environmentService, i18nService);
} }
} }

View File

@@ -1,46 +1,43 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { import { RouterModule, Routes } from "@angular/router";
RouterModule,
Routes,
} from '@angular/router';
import { AuthGuardService } from './services/auth-guard.service'; import { AuthGuardService } from "./services/auth-guard.service";
import { LaunchGuardService } from './services/launch-guard.service'; import { LaunchGuardService } from "./services/launch-guard.service";
import { ApiKeyComponent } from './accounts/apiKey.component'; import { ApiKeyComponent } from "./accounts/apiKey.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";
import { SettingsComponent } from './tabs/settings.component'; import { SettingsComponent } from "./tabs/settings.component";
import { TabsComponent } from './tabs/tabs.component'; import { TabsComponent } from "./tabs/tabs.component";
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' }, { path: "", redirectTo: "/login", pathMatch: "full" },
{ {
path: 'login', path: "login",
component: ApiKeyComponent, component: ApiKeyComponent,
canActivate: [LaunchGuardService], canActivate: [LaunchGuardService],
}, },
{ {
path: 'tabs', path: "tabs",
component: TabsComponent, component: TabsComponent,
children: [ children: [
{ {
path: '', path: "",
redirectTo: '/tabs/dashboard', redirectTo: "/tabs/dashboard",
pathMatch: 'full', pathMatch: "full",
}, },
{ {
path: 'dashboard', path: "dashboard",
component: DashboardComponent, component: DashboardComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
{ {
path: 'settings', path: "settings",
component: SettingsComponent, component: SettingsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
{ {
path: 'more', path: "more",
component: MoreComponent, component: MoreComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
}, },
@@ -49,10 +46,12 @@ const routes: Routes = [
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { imports: [
RouterModule.forRoot(routes, {
useHash: true, useHash: true,
/*enableTracing: true,*/ /*enableTracing: true,*/
})], }),
],
exports: [RouterModule], exports: [RouterModule],
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@@ -5,61 +5,65 @@ import {
SecurityContext, SecurityContext,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from "@angular/core";
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from "@angular/platform-browser";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { import { IndividualConfig, ToastrService } from "ngx-toastr";
IndividualConfig,
ToastrService,
} from 'ngx-toastr';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { ConfigurationService } from '../services/configuration.service'; import { ConfigurationService } from "../services/configuration.service";
import { SyncService } from '../services/sync.service'; import { SyncService } from "../services/sync.service";
const BroadcasterSubscriptionId = 'AppComponent'; const BroadcasterSubscriptionId = "AppComponent";
@Component({ @Component({
selector: 'app-root', selector: "app-root",
styles: [], styles: [],
template: ` template: ` <ng-template #settings></ng-template>
<ng-template #settings></ng-template>
<router-outlet></router-outlet>`, <router-outlet></router-outlet>`,
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
constructor(private broadcasterService: BroadcasterService, private userService: UserService, constructor(
private broadcasterService: BroadcasterService,
private userService: UserService,
private tokenService: TokenService, private tokenService: TokenService,
private authService: AuthService, private router: Router, private authService: AuthService,
private toastrService: ToastrService, private i18nService: I18nService, private router: Router,
private sanitizer: DomSanitizer, private ngZone: NgZone, private toastrService: ToastrService,
private platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, private i18nService: I18nService,
private configurationService: ConfigurationService, private syncService: SyncService, private sanitizer: DomSanitizer,
private stateService: StateService, private logService: LogService) { private ngZone: NgZone,
} private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private configurationService: ConfigurationService,
private syncService: SyncService,
private stateService: StateService,
private logService: LogService
) {}
ngOnInit() { ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'syncScheduleStarted': case "syncScheduleStarted":
case 'syncScheduleStopped': case "syncScheduleStopped":
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted'); this.stateService.save("syncingDir", message.command === "syncScheduleStarted");
break; break;
case 'logout': case "logout":
this.logOut(!!message.expired); this.logOut(!!message.expired);
break; break;
case 'checkDirSync': case "checkDirSync":
try { try {
const syncConfig = await this.configurationService.getSync(); const syncConfig = await this.configurationService.getSync();
if (syncConfig.interval == null || syncConfig.interval < 5) { if (syncConfig.interval == null || syncConfig.interval < 5) {
@@ -94,13 +98,15 @@ export class AppComponent implements OnInit {
this.logService.error(e); this.logService.error(e);
} }
this.messagingService.send('scheduleNextDirSync'); this.messagingService.send("scheduleNextDirSync");
break; break;
case 'showToast': case "showToast":
this.showToast(message); this.showToast(message);
break; break;
case 'ssoCallback': case "ssoCallback":
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } }); this.router.navigate(["sso"], {
queryParams: { code: message.code, state: message.state },
});
break; break;
default: default:
} }
@@ -120,25 +126,30 @@ export class AppComponent implements OnInit {
this.authService.logOut(async () => { this.authService.logOut(async () => {
if (expired) { if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'), this.platformUtilsService.showToast(
this.i18nService.t('loginExpired')); "warning",
this.i18nService.t("loggedOut"),
this.i18nService.t("loginExpired")
);
} }
this.router.navigate(['login']); this.router.navigate(["login"]);
}); });
} }
private showToast(msg: any) { private showToast(msg: any) {
let message = ''; let message = "";
const options: Partial<IndividualConfig> = {}; const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') { if (typeof msg.text === "string") {
message = msg.text; message = msg.text;
} else if (msg.text.length === 1) { } else if (msg.text.length === 1) {
message = msg.text[0]; message = msg.text[0];
} else { } else {
msg.text.forEach((t: string) => msg.text.forEach(
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>')); (t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true; options.enableHtml = true;
} }
if (msg.options != null) { if (msg.options != null) {
@@ -150,6 +161,6 @@ export class AppComponent implements OnInit {
} }
} }
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type); this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
} }
} }

View File

@@ -1,38 +1,38 @@
import 'core-js/stable'; import "core-js/stable";
import 'zone.js/dist/zone'; import "zone.js/dist/zone";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from './services/services.module'; import { ServicesModule } from "./services/services.module";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule } from '@angular/forms'; import { FormsModule } from "@angular/forms";
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 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 { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from 'jslib-angular/components/icon.component'; import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { ApiKeyComponent } from './accounts/apiKey.component'; import { ApiKeyComponent } from "./accounts/apiKey.component";
import { EnvironmentComponent } from './accounts/environment.component'; import { EnvironmentComponent } from "./accounts/environment.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";
import { SettingsComponent } from './tabs/settings.component'; import { SettingsComponent } from "./tabs/settings.component";
import { TabsComponent } from './tabs/tabs.component'; import { TabsComponent } from "./tabs/tabs.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive'; import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive'; import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive'; import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive'; import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive'; import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive'; import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive'; import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive'; import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe'; import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe'; import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
@NgModule({ @NgModule({
imports: [ imports: [

View File

@@ -1,12 +1,12 @@
import { enableProdMode } from '@angular/core'; import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { isDev } from 'jslib-electron/utils'; import { isDev } from "jslib-electron/utils";
// tslint:disable-next-line // tslint:disable-next-line
require('../scss/styles.scss'); require("../scss/styles.scss");
import { AppModule } from './app.module'; import { AppModule } from "./app.module";
if (!isDev()) { if (!isDev()) {
enableProdMode(); enableProdMode();

View File

@@ -1,21 +1,21 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { import { CanActivate, Router } from "@angular/router";
CanActivate, import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
Router,
} from '@angular/router';
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
@Injectable() @Injectable()
export class AuthGuardService implements CanActivate { export class AuthGuardService implements CanActivate {
constructor(private apiKeyService: ApiKeyService, private router: Router, constructor(
private messagingService: MessagingService) { } private apiKeyService: ApiKeyService,
private router: Router,
private messagingService: MessagingService
) {}
async canActivate() { async canActivate() {
const isAuthed = await this.apiKeyService.isAuthenticated(); const isAuthed = await this.apiKeyService.isAuthenticated();
if (!isAuthed) { if (!isAuthed) {
this.messagingService.send('logout'); this.messagingService.send("logout");
return false; return false;
} }

View File

@@ -1,10 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { import { CanActivate, Router } from "@angular/router";
CanActivate,
Router,
} from '@angular/router';
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
@Injectable() @Injectable()
export class LaunchGuardService implements CanActivate { export class LaunchGuardService implements CanActivate {
@@ -16,7 +13,7 @@ export class LaunchGuardService implements CanActivate {
return true; return true;
} }
this.router.navigate(['/tabs/dashboard']); this.router.navigate(["/tabs/dashboard"]);
return false; return false;
} }
} }

View File

@@ -1,58 +1,52 @@
import { import { APP_INITIALIZER, Injector, NgModule } from "@angular/core";
APP_INITIALIZER,
Injector,
NgModule,
} from '@angular/core';
import { ElectronLogService } from 'jslib-electron/services/electronLog.service'; import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service'; import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service'; import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service'; import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service'; import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { AuthGuardService } from './auth-guard.service'; import { AuthGuardService } from "./auth-guard.service";
import { LaunchGuardService } from './launch-guard.service'; import { LaunchGuardService } from "./launch-guard.service";
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from "../../services/configuration.service";
import { I18nService } from '../../services/i18n.service'; import { I18nService } from "../../services/i18n.service";
import { SyncService } from '../../services/sync.service'; import { SyncService } from "../../services/sync.service";
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service'; import { BroadcasterService } from "jslib-angular/services/broadcaster.service";
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module'; import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiKeyService } from 'jslib-common/services/apiKey.service'; import { ApiKeyService } from "jslib-common/services/apiKey.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service'; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service'; import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { ApiKeyService as ApiKeyServiceAbstraction } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService as ApiKeyServiceAbstraction } from "jslib-common/abstractions/apiKey.service";
import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service'; import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service'; import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service'; import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service";
PasswordGenerationService as PasswordGenerationServiceAbstraction, import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
} from 'jslib-common/abstractions/passwordGeneration.service'; import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service'; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { UserService as UserServiceAbstraction } from "jslib-common/abstractions/user.service";
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService, refreshToken } from '../../services/api.service'; import { ApiService, refreshToken } from "../../services/api.service";
import { AuthService } from '../../services/auth.service'; import { AuthService } from "../../services/auth.service";
function refreshTokenCallback(injector: Injector) { function refreshTokenCallback(injector: Injector) {
return () => { return () => {
@@ -62,26 +56,33 @@ function refreshTokenCallback(injector: Injector) {
}; };
} }
export function initFactory(environmentService: EnvironmentServiceAbstraction, export function initFactory(
i18nService: I18nService, authService: AuthService, platformUtilsService: PlatformUtilsServiceAbstraction, environmentService: EnvironmentServiceAbstraction,
storageService: StorageServiceAbstraction, userService: UserServiceAbstraction, apiService: ApiServiceAbstraction, i18nService: I18nService,
stateService: StateServiceAbstraction, cryptoService: CryptoServiceAbstraction): Function { authService: AuthService,
platformUtilsService: PlatformUtilsServiceAbstraction,
storageService: StorageServiceAbstraction,
userService: UserServiceAbstraction,
apiService: ApiServiceAbstraction,
stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction
): Function {
return async () => { return async () => {
await environmentService.setUrlsFromStorage(); await environmentService.setUrlsFromStorage();
await i18nService.init(); await i18nService.init();
authService.init(); authService.init();
const htmlEl = window.document.documentElement; const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString()); htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale); htmlEl.classList.add("locale_" + i18nService.translationLocale);
window.document.title = i18nService.t('bitwardenDirectoryConnector'); window.document.title = i18nService.t("bitwardenDirectoryConnector");
let installAction = null; let installAction = null;
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey); const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
const currentVersion = await platformUtilsService.getApplicationVersion(); const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) { if (installedVersion == null) {
installAction = 'install'; installAction = "install";
} else if (installedVersion !== currentVersion) { } else if (installedVersion !== currentVersion) {
installAction = 'update'; installAction = "update";
} }
if (installAction != null) { if (installAction != null) {
@@ -91,7 +92,7 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
window.setTimeout(async () => { window.setTimeout(async () => {
if (await userService.isAuthenticated()) { if (await userService.isAuthenticated()) {
const profile = await apiService.getProfile(); const profile = await apiService.getProfile();
stateService.save('profileOrganizations', profile.organizations); stateService.save("profileOrganizations", profile.organizations);
} }
}, 500); }, 500);
@@ -101,9 +102,7 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
} }
@NgModule({ @NgModule({
imports: [ imports: [JslibServicesModule],
JslibServicesModule,
],
declarations: [], declarations: [],
providers: [ providers: [
{ {
@@ -125,8 +124,8 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] }, { provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{ {
provide: I18nServiceAbstraction, provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, './locales'), useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: [ 'WINDOW' ], deps: ["WINDOW"],
}, },
{ {
provide: MessagingServiceAbstraction, provide: MessagingServiceAbstraction,
@@ -134,26 +133,37 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
deps: [BroadcasterServiceAbstraction], deps: [BroadcasterServiceAbstraction],
}, },
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService }, { provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: 'SECURE_STORAGE', useClass: ElectronRendererSecureStorageService }, { provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{ {
provide: PlatformUtilsServiceAbstraction, provide: PlatformUtilsServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction, useFactory: (
storageService: StorageServiceAbstraction) => new ElectronPlatformUtilsService(i18nService, i18nService: I18nServiceAbstraction,
messagingService, true, storageService), messagingService: MessagingServiceAbstraction,
deps: [ storageService: StorageServiceAbstraction
I18nServiceAbstraction, ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService),
MessagingServiceAbstraction, deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StorageServiceAbstraction],
StorageServiceAbstraction, },
], {
provide: CryptoFunctionServiceAbstraction,
useClass: NodeCryptoFunctionService,
deps: [],
}, },
{ provide: CryptoFunctionServiceAbstraction, useClass: NodeCryptoFunctionService, deps: [] },
{ {
provide: ApiServiceAbstraction, provide: ApiServiceAbstraction,
useFactory: (tokenService: TokenServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction, useFactory: (
environmentService: EnvironmentServiceAbstraction, messagingService: MessagingServiceAbstraction, tokenService: TokenServiceAbstraction,
injector: Injector) => platformUtilsService: PlatformUtilsServiceAbstraction,
new ApiService(tokenService, platformUtilsService, environmentService, refreshTokenCallback(injector), environmentService: EnvironmentServiceAbstraction,
async (expired: boolean) => messagingService.send('logout', { expired: expired })), messagingService: MessagingServiceAbstraction,
injector: Injector
) =>
new ApiService(
tokenService,
platformUtilsService,
environmentService,
refreshTokenCallback(injector),
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
deps: [ deps: [
TokenServiceAbstraction, TokenServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
@@ -165,10 +175,7 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
{ {
provide: ApiKeyServiceAbstraction, provide: ApiKeyServiceAbstraction,
useClass: ApiKeyService, useClass: ApiKeyService,
deps: [ deps: [TokenServiceAbstraction, StorageServiceAbstraction],
TokenServiceAbstraction,
StorageServiceAbstraction,
],
}, },
{ {
provide: AuthServiceAbstraction, provide: AuthServiceAbstraction,
@@ -193,10 +200,7 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
{ {
provide: ConfigurationService, provide: ConfigurationService,
useClass: ConfigurationService, useClass: ConfigurationService,
deps: [ deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
StorageServiceAbstraction,
'SECURE_STORAGE',
],
}, },
{ {
provide: SyncService, provide: SyncService,
@@ -215,5 +219,4 @@ export function initFactory(environmentService: EnvironmentServiceAbstraction,
LaunchGuardService, LaunchGuardService,
], ],
}) })
export class ServicesModule { export class ServicesModule {}
}

View File

@@ -1,97 +1,108 @@
<div class="card mb-3"> <div class="card mb-3">
<h3 class="card-header">{{'sync' | i18n}}</h3> <h3 class="card-header">{{ "sync" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<p> <p>
{{'lastGroupSync' | i18n}}: {{ "lastGroupSync" | i18n }}:
<span *ngIf="!lastGroupSync">-</span> <span *ngIf="!lastGroupSync">-</span>
{{lastGroupSync | date:'medium'}} {{ lastGroupSync | date: "medium" }}
<br /> {{'lastUserSync' | i18n}}: <br />
{{ "lastUserSync" | i18n }}:
<span *ngIf="!lastUserSync">-</span> <span *ngIf="!lastUserSync">-</span>
{{lastUserSync | date:'medium'}} {{ lastUserSync | date: "medium" }}
</p> </p>
<p> <p>
{{'syncStatus' | i18n}}: {{ "syncStatus" | i18n }}:
<strong *ngIf="syncRunning" class="text-success">{{'running' | i18n}}</strong> <strong *ngIf="syncRunning" class="text-success">{{ "running" | i18n }}</strong>
<strong *ngIf="!syncRunning" class="text-danger">{{'stopped' | i18n}}</strong> <strong *ngIf="!syncRunning" class="text-danger">{{ "stopped" | i18n }}</strong>
</p> </p>
<form #startForm [appApiAction]="startPromise" class="d-inline"> <form #startForm [appApiAction]="startPromise" class="d-inline">
<button (click)="start()" class="btn btn-primary" <button (click)="start()" class="btn btn-primary" [disabled]="startForm.loading">
[disabled]="startForm.loading">
<i class="fa fa-play fa-fw" [hidden]="startForm.loading"></i> <i class="fa fa-play fa-fw" [hidden]="startForm.loading"></i>
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startForm.loading"></i> <i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startForm.loading"></i>
{{'startSync' | i18n}} {{ "startSync" | i18n }}
</button> </button>
</form> </form>
<button (click)="stop()" class="btn btn-primary"> <button (click)="stop()" class="btn btn-primary">
<i class="fa fa-stop fa-fw"></i> <i class="fa fa-stop fa-fw"></i>
{{'stopSync' | i18n}} {{ "stopSync" | i18n }}
</button> </button>
<form #syncForm [appApiAction]="syncPromise" class="d-inline"> <form #syncForm [appApiAction]="syncPromise" class="d-inline">
<button (click)="sync()" class="btn btn-primary" <button (click)="sync()" class="btn btn-primary" [disabled]="syncForm.loading">
[disabled]="syncForm.loading">
<i class="fa fa-refresh fa-fw" [ngClass]="{ 'fa-spin': syncForm.loading }"></i> <i class="fa fa-refresh fa-fw" [ngClass]="{ 'fa-spin': syncForm.loading }"></i>
{{'syncNow' | i18n}} {{ "syncNow" | i18n }}
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h3 class="card-header">{{'testing' | i18n}}</h3> <h3 class="card-header">{{ "testing" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<p>{{'testingDesc' | i18n}}</p> <p>{{ "testingDesc" | i18n }}</p>
<form #simForm [appApiAction]="simPromise" class="d-inline"> <form #simForm [appApiAction]="simPromise" class="d-inline">
<button (click)="simulate()" class="btn btn-primary" <button (click)="simulate()" class="btn btn-primary" [disabled]="simForm.loading">
[disabled]="simForm.loading">
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simForm.loading"></i> <i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simForm.loading"></i>
<i class="fa fa-bug fa-fw" [hidden]="simForm.loading"></i> <i class="fa fa-bug fa-fw" [hidden]="simForm.loading"></i>
{{'testNow' | i18n}} {{ "testNow" | i18n }}
</button> </button>
</form> </form>
<div class="form-check mt-2"> <div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="simSinceLast" [(ngModel)]="simSinceLast"> <input
<label class="form-check-label" for="simSinceLast">{{'testLastSync' | i18n}}</label> class="form-check-input"
type="checkbox"
id="simSinceLast"
[(ngModel)]="simSinceLast"
/>
<label class="form-check-label" for="simSinceLast">{{ "testLastSync" | i18n }}</label>
</div> </div>
<ng-container *ngIf="!simForm.loading && (simUsers || simGroups)"> <ng-container *ngIf="!simForm.loading && (simUsers || simGroups)">
<hr /> <hr />
<div class="row"> <div class="row">
<div class="col-lg"> <div class="col-lg">
<h4>{{'users' | i18n}}</h4> <h4>{{ "users" | i18n }}</h4>
<ul class="fa-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length"> <ul class="fa-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length">
<li *ngFor="let u of simEnabledUsers" title="{{ u.referenceId }}"> <li *ngFor="let u of simEnabledUsers" title="{{ u.referenceId }}">
<i class="fa-li fa fa-user"></i> <i class="fa-li fa fa-user"></i>
{{ u.displayName }} {{ u.displayName }}
</li> </li>
</ul> </ul>
<p *ngIf="!simEnabledUsers || !simEnabledUsers.length">{{'noUsers' | i18n}}</p> <p *ngIf="!simEnabledUsers || !simEnabledUsers.length">
<h4>{{'disabledUsers' | i18n}}</h4> {{ "noUsers" | i18n }}
</p>
<h4>{{ "disabledUsers" | i18n }}</h4>
<ul class="fa-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length"> <ul class="fa-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length">
<li *ngFor="let u of simDisabledUsers" title="{{ u.referenceId }}"> <li *ngFor="let u of simDisabledUsers" title="{{ u.referenceId }}">
<i class="fa-li fa fa-user"></i> <i class="fa-li fa fa-user"></i>
{{ u.displayName }} {{ u.displayName }}
</li> </li>
</ul> </ul>
<p *ngIf="!simDisabledUsers || !simDisabledUsers.length">{{'noUsers' | i18n}}</p> <p *ngIf="!simDisabledUsers || !simDisabledUsers.length">
<h4>{{'deletedUsers' | i18n}}</h4> {{ "noUsers" | i18n }}
</p>
<h4>{{ "deletedUsers" | i18n }}</h4>
<ul class="fa-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length"> <ul class="fa-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length">
<li *ngFor="let u of simDeletedUsers" title="{{ u.referenceId }}"> <li *ngFor="let u of simDeletedUsers" title="{{ u.referenceId }}">
<i class="fa-li fa fa-user"></i> <i class="fa-li fa fa-user"></i>
{{ u.displayName }} {{ u.displayName }}
</li> </li>
</ul> </ul>
<p *ngIf="!simDeletedUsers || !simDeletedUsers.length">{{'noUsers' | i18n}}</p> <p *ngIf="!simDeletedUsers || !simDeletedUsers.length">
{{ "noUsers" | i18n }}
</p>
</div> </div>
<div class="col-lg"> <div class="col-lg">
<h4>{{'groups' | i18n}}</h4> <h4>{{ "groups" | i18n }}</h4>
<ul class="fa-ul testing-list" *ngIf="simGroups && simGroups.length"> <ul class="fa-ul testing-list" *ngIf="simGroups && simGroups.length">
<li *ngFor="let g of simGroups" title="{{ g.referenceId }}"> <li *ngFor="let g of simGroups" title="{{ g.referenceId }}">
<i class="fa-li fa fa-sitemap"></i> <i class="fa-li fa fa-sitemap"></i>
{{ g.displayName }} {{ g.displayName }}
<ul class="small" *ngIf="g.users && g.users.length"> <ul class="small" *ngIf="g.users && g.users.length">
<li *ngFor="let u of g.users" title="{{u.referenceId}}">{{u.displayName}}</li> <li *ngFor="let u of g.users" title="{{ u.referenceId }}">
{{ u.displayName }}
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
<p *ngIf="!simGroups || !simGroups.length">{{'noGroups' | i18n}}</p> <p *ngIf="!simGroups || !simGroups.length">{{ "noGroups" | i18n }}</p>
</div> </div>
</div> </div>
</ng-container> </ng-container>

View File

@@ -1,31 +1,25 @@
import { import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from '../../services/sync.service'; import { SyncService } from "../../services/sync.service";
import { GroupEntry } from '../../models/groupEntry'; import { GroupEntry } from "../../models/groupEntry";
import { SimResult } from '../../models/simResult'; import { SimResult } from "../../models/simResult";
import { UserEntry } from '../../models/userEntry'; import { UserEntry } from "../../models/userEntry";
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from "../../services/configuration.service";
import { ConnectorUtils } from '../../utils'; import { ConnectorUtils } from "../../utils";
const BroadcasterSubscriptionId = 'DashboardComponent'; const BroadcasterSubscriptionId = "DashboardComponent";
@Component({ @Component({
selector: 'app-dashboard', selector: "app-dashboard",
templateUrl: 'dashboard.component.html', templateUrl: "dashboard.component.html",
}) })
export class DashboardComponent implements OnInit, OnDestroy { export class DashboardComponent implements OnInit, OnDestroy {
simGroups: GroupEntry[]; simGroups: GroupEntry[];
@@ -41,17 +35,23 @@ export class DashboardComponent implements OnInit, OnDestroy {
lastUserSync: Date; lastUserSync: Date;
syncRunning: boolean; syncRunning: boolean;
constructor(private i18nService: I18nService, private syncService: SyncService, constructor(
private configurationService: ConfigurationService, private broadcasterService: BroadcasterService, private i18nService: I18nService,
private ngZone: NgZone, private messagingService: MessagingService, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService, private changeDetectorRef: ChangeDetectorRef, private configurationService: ConfigurationService,
private stateService: StateService) { } private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService
) {}
async ngOnInit() { async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'dirSyncCompleted': case "dirSyncCompleted":
this.updateLastSync(); this.updateLastSync();
break; break;
default: default:
@@ -62,7 +62,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
}); });
}); });
this.syncRunning = !!(await this.stateService.get('syncingDir')); this.syncRunning = !!(await this.stateService.get("syncingDir"));
this.updateLastSync(); this.updateLastSync();
} }
@@ -73,15 +73,15 @@ export class DashboardComponent implements OnInit, OnDestroy {
async start() { async start() {
this.startPromise = this.syncService.sync(false, false); this.startPromise = this.syncService.sync(false, false);
await this.startPromise; await this.startPromise;
this.messagingService.send('scheduleNextDirSync'); this.messagingService.send("scheduleNextDirSync");
this.syncRunning = true; this.syncRunning = true;
this.platformUtilsService.showToast('success', null, this.i18nService.t('syncingStarted')); this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStarted"));
} }
async stop() { async stop() {
this.messagingService.send('cancelDirSync'); this.messagingService.send("cancelDirSync");
this.syncRunning = false; this.syncRunning = false;
this.platformUtilsService.showToast('success', null, this.i18nService.t('syncingStopped')); this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStopped"));
} }
async sync() { async sync() {
@@ -89,8 +89,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
const result = await this.syncPromise; const result = await this.syncPromise;
const groupCount = result[0] != null ? result[0].length : 0; const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0; const userCount = result[1] != null ? result[1].length : 0;
this.platformUtilsService.showToast('success', null, this.platformUtilsService.showToast(
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString())); "success",
null,
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
);
} }
async simulate() { async simulate() {
@@ -101,7 +104,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.simDeletedUsers = []; this.simDeletedUsers = [];
try { try {
this.simPromise = ConnectorUtils.simulate(this.syncService, this.i18nService, this.simSinceLast); this.simPromise = ConnectorUtils.simulate(
this.syncService,
this.i18nService,
this.simSinceLast
);
const result = await this.simPromise; const result = await this.simPromise;
this.simGroups = result.groups; this.simGroups = result.groups;
this.simUsers = result.users; this.simUsers = result.users;

View File

@@ -1,30 +1,36 @@
<div class="row"> <div class="row">
<div class="col-sm"> <div class="col-sm">
<div class="card"> <div class="card">
<h3 class="card-header">{{'about' | i18n}}</h3> <h3 class="card-header">{{ "about" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<p> <p>
{{'bitwardenDirectoryConnector' | i18n}} {{ "bitwardenDirectoryConnector" | i18n }}
<br /> {{'version' | i18n : version}} <br />
<br /> &copy; Bitwarden Inc. LLC 2015-{{year}} {{ "version" | i18n: version }} <br />
&copy; 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>
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!checkingForUpdate"></i> <i class="fa fa-spinner fa-fw fa-spin" [hidden]="!checkingForUpdate"></i>
{{'checkForUpdates' | i18n}} {{ "checkForUpdates" | i18n }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<div class="card"> <div class="card">
<h3 class="card-header">{{'other' | i18n}}</h3> <h3 class="card-header">{{ "other" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<button class="btn btn-primary" type="button" (click)="logOut()"> <button class="btn btn-primary" type="button" (click)="logOut()">
{{'logOut' | i18n}} {{ "logOut" | i18n }}
</button> </button>
<button class="btn btn-primary" type="button" (click)="clearCache()"> <button class="btn btn-primary" type="button" (click)="clearCache()">
{{'clearSyncCache' | i18n}} {{ "clearSyncCache" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,42 +1,41 @@
import { import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from "../../services/configuration.service";
const BroadcasterSubscriptionId = 'MoreComponent'; const BroadcasterSubscriptionId = "MoreComponent";
@Component({ @Component({
selector: 'app-more', selector: "app-more",
templateUrl: 'more.component.html', templateUrl: "more.component.html",
}) })
export class MoreComponent implements OnInit { export class MoreComponent implements OnInit {
version: string; version: string;
year: string; year: string;
checkingForUpdate = false; checkingForUpdate = false;
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, constructor(
private messagingService: MessagingService, private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private messagingService: MessagingService,
private configurationService: ConfigurationService,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { } private ngZone: NgZone,
private changeDetectorRef: ChangeDetectorRef
) {}
async ngOnInit() { async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'checkingForUpdate': case "checkingForUpdate":
this.checkingForUpdate = true; this.checkingForUpdate = true;
break; break;
case 'doneCheckingForUpdate': case "doneCheckingForUpdate":
this.checkingForUpdate = false; this.checkingForUpdate = false;
break; break;
default: default:
@@ -56,20 +55,23 @@ export class MoreComponent implements OnInit {
} }
update() { update() {
this.messagingService.send('checkForUpdate'); this.messagingService.send("checkForUpdate");
} }
async logOut() { async logOut() {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'), this.i18nService.t("logOutConfirmation"),
this.i18nService.t('yes'), this.i18nService.t('cancel')); this.i18nService.t("logOut"),
this.i18nService.t("yes"),
this.i18nService.t("cancel")
);
if (confirmed) { if (confirmed) {
this.messagingService.send('logout'); this.messagingService.send("logout");
} }
} }
async clearCache() { async clearCache() {
await this.configurationService.clearStatefulSettings(true); await this.configurationService.clearStatefulSettings(true);
this.platformUtilsService.showToast('success', null, this.i18nService.t('syncCacheCleared')); this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared"));
} }
} }

View File

@@ -1,132 +1,245 @@
<div class="row"> <div class="row">
<div class="col-sm"> <div class="col-sm">
<div class="card mb-3"> <div class="card mb-3">
<h3 class="card-header">{{'directory' | i18n}}</h3> <h3 class="card-header">{{ "directory" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="directory">{{'type' | i18n}}</label> <label for="directory">{{ "type" | i18n }}</label>
<select class="form-control" id="directory" name="Directory" [(ngModel)]="directory"> <select class="form-control" id="directory" name="Directory" [(ngModel)]="directory">
<option *ngFor="let o of directoryOptions" [ngValue]="o.value">{{o.name}}</option> <option *ngFor="let o of directoryOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select> </select>
</div> </div>
<div [hidden]="directory != directoryType.Ldap"> <div [hidden]="directory != directoryType.Ldap">
<div class="form-group"> <div class="form-group">
<label for="hostname">{{'serverHostname' | i18n}}</label> <label for="hostname">{{ "serverHostname" | i18n }}</label>
<input type="text" class="form-control" id="hostname" name="Hostname" <input
[(ngModel)]="ldap.hostname"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} ad.company.com</small> class="form-control"
id="hostname"
name="Hostname"
[(ngModel)]="ldap.hostname"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} ad.company.com</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="port">{{'port' | i18n}}</label> <label for="port">{{ "port" | i18n }}</label>
<input type="text" class="form-control" id="port" name="Port" [(ngModel)]="ldap.port"> <input type="text" class="form-control" id="port" name="Port" [(ngModel)]="ldap.port" />
<small class="text-muted form-text">{{'ex' | i18n}} 389</small> <small class="text-muted form-text">{{ "ex" | i18n }} 389</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rootPath">{{'rootPath' | i18n}}</label> <label for="rootPath">{{ "rootPath" | i18n }}</label>
<input type="text" class="form-control" id="rootPath" name="RootPath" <input
[(ngModel)]="ldap.rootPath"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} dc=company,dc=com</small> class="form-control"
id="rootPath"
name="RootPath"
[(ngModel)]="ldap.rootPath"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} dc=company,dc=com</small>
</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="ad" [(ngModel)]="ldap.ad" name="AD"> <input
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label> class="form-check-input"
type="checkbox"
id="ad"
[(ngModel)]="ldap.ad"
name="AD"
/>
<label class="form-check-label" for="ad">{{ "ldapAd" | i18n }}</label>
</div> </div>
</div> </div>
<div class="form-group" *ngIf="!ldap.ad"> <div class="form-group" *ngIf="!ldap.ad">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="pagedSearch" <input
[(ngModel)]="ldap.pagedSearch" name="PagedSearch"> class="form-check-input"
<label class="form-check-label" for="pagedSearch">{{'ldapPagedResults' | i18n}}</label> type="checkbox"
id="pagedSearch"
[(ngModel)]="ldap.pagedSearch"
name="PagedSearch"
/>
<label class="form-check-label" for="pagedSearch">{{
"ldapPagedResults" | i18n
}}</label>
</div> </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="ldapEncrypted" [(ngModel)]="ldap.ssl" <input
name="Encrypted"> class="form-check-input"
<label class="form-check-label" for="ldapEncrypted">{{'ldapEncrypted' | i18n}}</label> type="checkbox"
id="ldapEncrypted"
[(ngModel)]="ldap.ssl"
name="Encrypted"
/>
<label class="form-check-label" for="ldapEncrypted">{{
"ldapEncrypted" | i18n
}}</label>
</div> </div>
</div> </div>
<div class="ml-4" *ngIf="ldap.ssl"> <div class="ml-4" *ngIf="ldap.ssl">
<div class="form-group"> <div class="form-group">
<div class="form-radio"> <div class="form-radio">
<input class="form-radio-input" type="radio" [value]="false" id="ssl" <input
[(ngModel)]="ldap.startTls" name="SSL"> class="form-radio-input"
<label class="form-radio-label" for="ssl">{{'ldapSsl' | i18n}}</label> type="radio"
[value]="false"
id="ssl"
[(ngModel)]="ldap.startTls"
name="SSL"
/>
<label class="form-radio-label" for="ssl">{{ "ldapSsl" | i18n }}</label>
</div> </div>
<div class="form-radio"> <div class="form-radio">
<input class="form-radio-input" type="radio" [value]="true" id="startTls" <input
[(ngModel)]="ldap.startTls" name="StartTLS"> class="form-radio-input"
<label class="form-radio-label" for="startTls">{{'ldapTls' | i18n}}</label> 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>
<div class="ml-4" *ngIf="ldap.startTls"> <div class="ml-4" *ngIf="ldap.startTls">
<p>{{'ldapTlsUntrustedDesc' | i18n}}</p> <p>{{ "ldapTlsUntrustedDesc" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="tlsCaPath">{{'ldapTlsCa' | i18n}}</label> <label for="tlsCaPath">{{ "ldapTlsCa" | i18n }}</label>
<input type="file" class="form-control-file mb-2" id="tlsCaPath_file" <input
(change)="setSslPath('tlsCaPath')"> type="file"
<input type="text" class="form-control" id="tlsCaPath" name="TLSCaPath" class="form-control-file mb-2"
[(ngModel)]="ldap.tlsCaPath"> id="tlsCaPath_file"
(change)="setSslPath('tlsCaPath')"
/>
<input
type="text"
class="form-control"
id="tlsCaPath"
name="TLSCaPath"
[(ngModel)]="ldap.tlsCaPath"
/>
</div> </div>
</div> </div>
<div class="ml-4" *ngIf="!ldap.startTls"> <div class="ml-4" *ngIf="!ldap.startTls">
<p>{{'ldapSslUntrustedDesc' | i18n}}</p> <p>{{ "ldapSslUntrustedDesc" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label> <label for="sslCertPath">{{ "ldapSslCert" | i18n }}</label>
<input type="file" class="form-control-file mb-2" id="sslCertPath_file" <input
(change)="setSslPath('sslCertPath')"> type="file"
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath" class="form-control-file mb-2"
[(ngModel)]="ldap.sslCertPath"> id="sslCertPath_file"
(change)="setSslPath('sslCertPath')"
/>
<input
type="text"
class="form-control"
id="sslCertPath"
name="SSLCertPath"
[(ngModel)]="ldap.sslCertPath"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label> <label for="sslKeyPath">{{ "ldapSslKey" | i18n }}</label>
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file" <input
(change)="setSslPath('sslKeyPath')"> type="file"
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath" class="form-control-file mb-2"
[(ngModel)]="ldap.sslKeyPath"> id="sslKeyPath_file"
(change)="setSslPath('sslKeyPath')"
/>
<input
type="text"
class="form-control"
id="sslKeyPath"
name="SSLKeyPath"
[(ngModel)]="ldap.sslKeyPath"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label> <label for="sslCaPath">{{ "ldapSslCa" | i18n }}</label>
<input type="file" class="form-control-file mb-2" id="sslCaPath_file" <input
(change)="setSslPath('sslCaPath')"> type="file"
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath" class="form-control-file mb-2"
[(ngModel)]="ldap.sslCaPath"> id="sslCaPath_file"
(change)="setSslPath('sslCaPath')"
/>
<input
type="text"
class="form-control"
id="sslCaPath"
name="SSLCaPath"
[(ngModel)]="ldap.sslCaPath"
/>
</div> </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="certDoNotVerify" <input
[(ngModel)]="ldap.sslAllowUnauthorized" name="CertDoNoVerify"> class="form-check-input"
<label class="form-check-label" for="certDoNotVerify">{{'ldapCertDoNotVerify' | type="checkbox"
i18n}}</label> id="certDoNotVerify"
[(ngModel)]="ldap.sslAllowUnauthorized"
name="CertDoNoVerify"
/>
<label class="form-check-label" for="certDoNotVerify">{{
"ldapCertDoNotVerify" | i18n
}}</label>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group" [hidden]="true"> <div class="form-group" [hidden]="true">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="currentUser" <input
[(ngModel)]="ldap.currentUser" name="CurrentUser"> class="form-check-input"
<label class="form-check-label" for="currentUser">{{'currentUser' | i18n}}</label> type="checkbox"
id="currentUser"
[(ngModel)]="ldap.currentUser"
name="CurrentUser"
/>
<label class="form-check-label" for="currentUser">{{ "currentUser" | i18n }}</label>
</div> </div>
</div> </div>
<div [hidden]="ldap.currentUser"> <div [hidden]="ldap.currentUser">
<div class="form-group"> <div class="form-group">
<label for="username">{{'username' | i18n}}</label> <label for="username">{{ "username" | i18n }}</label>
<input type="text" class="form-control" id="username" name="Username" <input
[(ngModel)]="ldap.username"> type="text"
<small class="text-muted form-text" *ngIf="ldap.ad">{{'ex' | i18n}} company\admin</small> class="form-control"
<small class="text-muted form-text" *ngIf="!ldap.ad">{{'ex' | i18n}} id="username"
cn=admin,dc=company,dc=com</small> name="Username"
[(ngModel)]="ldap.username"
/>
<small class="text-muted form-text" *ngIf="ldap.ad"
>{{ "ex" | i18n }} company\admin</small
>
<small class="text-muted form-text" *ngIf="!ldap.ad"
>{{ "ex" | i18n }} cn=admin,dc=company,dc=com</small
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">{{'password' | i18n}}</label> <label for="password">{{ "password" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input type="{{showLdapPassword ? 'text' : 'password'}}" class="form-control" id="password" name="Password" <input
[(ngModel)]="ldap.password"> type="{{ showLdapPassword ? 'text' : 'password' }}"
class="form-control"
id="password"
name="Password"
[(ngModel)]="ldap.password"
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleLdapPassword()"> <button
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showLdapPassword ? 'fa-eye-slash' : 'fa-eye'"></i> type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleLdapPassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="showLdapPassword ? 'fa-eye-slash' : 'fa-eye'"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -135,31 +248,60 @@
</div> </div>
<div [hidden]="directory != directoryType.AzureActiveDirectory"> <div [hidden]="directory != directoryType.AzureActiveDirectory">
<div class="form-group"> <div class="form-group">
<label for="identityAuthority">{{'identityAuthority' | i18n}}</label> <label for="identityAuthority">{{ "identityAuthority" | i18n }}</label>
<select class="form-control" id="identityAuthority" name="IdentityAuthority" <select
[(ngModel)]="azure.identityAuthority"> class="form-control"
id="identityAuthority"
name="IdentityAuthority"
[(ngModel)]="azure.identityAuthority"
>
<option value="login.microsoftonline.com">Azure AD Public</option> <option value="login.microsoftonline.com">Azure AD Public</option>
<option value="login.microsoftonline.us">Azure AD Government</option> <option value="login.microsoftonline.us">Azure AD Government</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="tenant">{{'tenant' | i18n}}</label> <label for="tenant">{{ "tenant" | i18n }}</label>
<input type="text" class="form-control" id="tenant" name="Tenant" [(ngModel)]="azure.tenant"> <input
<small class="text-muted form-text">{{'ex' | i18n}} companyad.onmicrosoft.com</small> type="text"
class="form-control"
id="tenant"
name="Tenant"
[(ngModel)]="azure.tenant"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} companyad.onmicrosoft.com</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="applicationId">{{'applicationId' | i18n}}</label> <label for="applicationId">{{ "applicationId" | i18n }}</label>
<input type="text" class="form-control" id="applicationId" name="ApplicationId" <input
[(ngModel)]="azure.applicationId"> type="text"
class="form-control"
id="applicationId"
name="ApplicationId"
[(ngModel)]="azure.applicationId"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="secretKey">{{'secretKey' | i18n}}</label> <label for="secretKey">{{ "secretKey" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input type="{{showAzureKey ? 'text' : 'password'}}" class="form-control" id="secretKey" name="SecretKey" <input
[(ngModel)]="azure.key"> type="{{ showAzureKey ? 'text' : 'password' }}"
class="form-control"
id="secretKey"
name="SecretKey"
[(ngModel)]="azure.key"
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleAzureKey()"> <button
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showAzureKey ? 'fa-eye-slash' : 'fa-eye'"></i> type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleAzureKey()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="showAzureKey ? 'fa-eye-slash' : 'fa-eye'"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -167,18 +309,40 @@
</div> </div>
<div [hidden]="directory != directoryType.Okta"> <div [hidden]="directory != directoryType.Okta">
<div class="form-group"> <div class="form-group">
<label for="orgUrl">{{'organizationUrl' | i18n}}</label> <label for="orgUrl">{{ "organizationUrl" | i18n }}</label>
<input type="text" class="form-control" id="orgUrl" name="OrgUrl" [(ngModel)]="okta.orgUrl"> <input
<small class="text-muted form-text">{{'ex' | i18n}} https://mycompany.okta.com/</small> type="text"
class="form-control"
id="orgUrl"
name="OrgUrl"
[(ngModel)]="okta.orgUrl"
/>
<small class="text-muted form-text"
>{{ "ex" | i18n }} https://mycompany.okta.com/</small
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="oktaToken">{{'token' | i18n}}</label> <label for="oktaToken">{{ "token" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input type="{{showOktaKey ? 'text' : 'password'}}" class="form-control" id="oktaToken" name="OktaToken" <input
[(ngModel)]="okta.token"> type="{{ showOktaKey ? 'text' : 'password' }}"
class="form-control"
id="oktaToken"
name="OktaToken"
[(ngModel)]="okta.token"
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOktaKey()"> <button
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOktaKey ? 'fa-eye-slash' : 'fa-eye'"></i> type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleOktaKey()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="showOktaKey ? 'fa-eye-slash' : 'fa-eye'"
></i>
</button> </button>
</div> </div>
</div> </div>
@@ -186,26 +350,49 @@
</div> </div>
<div [hidden]="directory != directoryType.OneLogin"> <div [hidden]="directory != directoryType.OneLogin">
<div class="form-group"> <div class="form-group">
<label for="oneLoginClientId">{{'clientId' | i18n}}</label> <label for="oneLoginClientId">{{ "clientId" | i18n }}</label>
<input type="text" class="form-control" id="oneLoginClientId" name="OneLoginClientId" <input
[(ngModel)]="oneLogin.clientId"> type="text"
class="form-control"
id="oneLoginClientId"
name="OneLoginClientId"
[(ngModel)]="oneLogin.clientId"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label> <label for="oneLoginClientSecret">{{ "clientSecret" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input type="{{showOneLoginSecret ? 'text' : 'password'}}" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret" <input
[(ngModel)]="oneLogin.clientSecret"> type="{{ showOneLoginSecret ? 'text' : 'password' }}"
class="form-control"
id="oneLoginClientSecret"
name="OneLoginClientSecret"
[(ngModel)]="oneLogin.clientSecret"
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOneLoginSecret()"> <button
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOneLoginSecret ? 'fa-eye-slash' : 'fa-eye'"></i> type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleOneLoginSecret()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="showOneLoginSecret ? 'fa-eye-slash' : 'fa-eye'"
></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="oneLoginRegion">{{'region' | i18n}}</label> <label for="oneLoginRegion">{{ "region" | i18n }}</label>
<select class="form-control" id="oneLoginRegion" name="OneLoginRegion" <select
[(ngModel)]="oneLogin.region"> class="form-control"
id="oneLoginRegion"
name="OneLoginRegion"
[(ngModel)]="oneLogin.region"
>
<option value="us">US</option> <option value="us">US</option>
<option value="eu">EU</option> <option value="eu">EU</option>
</select> </select>
@@ -213,209 +400,354 @@
</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>
<input type="text" class="form-control" id="domain" name="Domain" [(ngModel)]="gsuite.domain"> <input
<small class="text-muted form-text">{{'ex' | i18n}} company.com</small> type="text"
class="form-control"
id="domain"
name="Domain"
[(ngModel)]="gsuite.domain"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} company.com</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="adminUser">{{'adminUser' | i18n}}</label> <label for="adminUser">{{ "adminUser" | i18n }}</label>
<input type="text" class="form-control" id="adminUser" name="AdminUser" <input
[(ngModel)]="gsuite.adminUser"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} admin@company.com</small> class="form-control"
id="adminUser"
name="AdminUser"
[(ngModel)]="gsuite.adminUser"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} admin@company.com</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="customerId">{{'customerId' | i18n}}</label> <label for="customerId">{{ "customerId" | i18n }}</label>
<input type="text" class="form-control" id="customerId" name="CustomerId" <input
[(ngModel)]="gsuite.customer"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} 39204722352</small> class="form-control"
id="customerId"
name="CustomerId"
[(ngModel)]="gsuite.customer"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} 39204722352</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="keyFile">{{'jsonKeyFile' | i18n}}</label> <label for="keyFile">{{ "jsonKeyFile" | i18n }}</label>
<input type="file" class="form-control-file" id="keyFile" accept=".json" <input
(change)="parseKeyFile()"> type="file"
<small class="text-muted form-text">{{'ex' | i18n}} My Project-jksd3jd223.json</small> class="form-control-file"
id="keyFile"
accept=".json"
(change)="parseKeyFile()"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} My Project-jksd3jd223.json</small>
</div> </div>
<div class="form-group" [hidden]="!gsuite.clientEmail"> <div class="form-group" [hidden]="!gsuite.clientEmail">
<label for="clientEmail">{{'clientEmail' | i18n}}</label> <label for="clientEmail">{{ "clientEmail" | i18n }}</label>
<input type="text" class="form-control" id="clientEmail" name="ClientEmail" <input
[(ngModel)]="gsuite.clientEmail"> type="text"
class="form-control"
id="clientEmail"
name="ClientEmail"
[(ngModel)]="gsuite.clientEmail"
/>
</div> </div>
<div class="form-group" [hidden]="!gsuite.privateKey"> <div class="form-group" [hidden]="!gsuite.privateKey">
<label for="privateKey">{{'privateKey' | i18n}}</label> <label for="privateKey">{{ "privateKey" | i18n }}</label>
<textarea class="form-control text-monospace" id="privateKey" name="PrivateKey" <textarea
[(ngModel)]="gsuite.privateKey"> class="form-control text-monospace"
id="privateKey"
name="PrivateKey"
[(ngModel)]="gsuite.privateKey"
>
</textarea> </textarea>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<div class="card"> <div class="card">
<h3 class="card-header">{{'sync' | i18n}}</h3> <h3 class="card-header">{{ "sync" | i18n }}</h3>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="interval">{{'interval' | i18n}}</label> <label for="interval">{{ "interval" | i18n }}</label>
<input type="number" min="5" class="form-control" id="interval" name="Interval" <input
[(ngModel)]="sync.interval"> type="number"
<small class="text-muted form-text">{{'intervalMin' | i18n}}</small> min="5"
class="form-control"
id="interval"
name="Interval"
[(ngModel)]="sync.interval"
/>
<small class="text-muted form-text">{{ "intervalMin" | i18n }}</small>
</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="removeDisabled" <input
[(ngModel)]="sync.removeDisabled" name="RemoveDisabled"> class="form-check-input"
<label class="form-check-label" for="removeDisabled">{{'removeDisabled' | i18n}}</label> type="checkbox"
id="removeDisabled"
[(ngModel)]="sync.removeDisabled"
name="RemoveDisabled"
/>
<label class="form-check-label" for="removeDisabled">{{
"removeDisabled" | i18n
}}</label>
</div> </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="overwriteExisting" <input
[(ngModel)]="sync.overwriteExisting" name="OverwriteExisting"> class="form-check-input"
<label class="form-check-label" for="overwriteExisting">{{'overwriteExisting' | i18n}}</label> type="checkbox"
id="overwriteExisting"
[(ngModel)]="sync.overwriteExisting"
name="OverwriteExisting"
/>
<label class="form-check-label" for="overwriteExisting">{{
"overwriteExisting" | i18n
}}</label>
</div> </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="largeImport" [(ngModel)]="sync.largeImport" <input
name="LargeImport"> class="form-check-input"
<label class="form-check-label" for="largeImport">{{'largeImport' | i18n}}</label> type="checkbox"
id="largeImport"
[(ngModel)]="sync.largeImport"
name="LargeImport"
/>
<label class="form-check-label" for="largeImport">{{ "largeImport" | i18n }}</label>
</div> </div>
</div> </div>
<div [hidden]="directory != directoryType.Ldap"> <div [hidden]="directory != directoryType.Ldap">
<div [hidden]="ldap.ad"> <div [hidden]="ldap.ad">
<div class="form-group"> <div class="form-group">
<label for="memberAttribute">{{'memberAttribute' | i18n}}</label> <label for="memberAttribute">{{ "memberAttribute" | i18n }}</label>
<input type="text" class="form-control" id="memberAttribute" name="MemberAttribute" <input
[(ngModel)]="sync.memberAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} uniqueMember</small> class="form-control"
id="memberAttribute"
name="MemberAttribute"
[(ngModel)]="sync.memberAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} uniqueMember</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="creationDateAttribute">{{'creationDateAttribute' | i18n}}</label> <label for="creationDateAttribute">{{ "creationDateAttribute" | i18n }}</label>
<input type="text" class="form-control" id="creationDateAttribute" <input
name="CreationDateAttribute" [(ngModel)]="sync.creationDateAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} whenCreated</small> class="form-control"
id="creationDateAttribute"
name="CreationDateAttribute"
[(ngModel)]="sync.creationDateAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} whenCreated</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="revisionDateAttribute">{{'revisionDateAttribute' | i18n}}</label> <label for="revisionDateAttribute">{{ "revisionDateAttribute" | i18n }}</label>
<input type="text" class="form-control" id="revisionDateAttribute" <input
name="RevisionDateAttribute" [(ngModel)]="sync.revisionDateAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small> class="form-control"
id="revisionDateAttribute"
name="RevisionDateAttribute"
[(ngModel)]="sync.revisionDateAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} whenChanged</small>
</div> </div>
</div> </div>
</div> </div>
<div [hidden]="directory != directoryType.Ldap && directory != directoryType.OneLogin"> <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
[(ngModel)]="sync.useEmailPrefixSuffix" name="UseEmailPrefixSuffix"> class="form-check-input"
<label class="form-check-label" type="checkbox"
for="useEmailPrefixSuffix">{{'useEmailPrefixSuffix' | i18n}}</label> id="useEmailPrefixSuffix"
[(ngModel)]="sync.useEmailPrefixSuffix"
name="UseEmailPrefixSuffix"
/>
<label class="form-check-label" for="useEmailPrefixSuffix">{{
"useEmailPrefixSuffix" | i18n
}}</label>
</div> </div>
</div> </div>
<div [hidden]="!sync.useEmailPrefixSuffix"> <div [hidden]="!sync.useEmailPrefixSuffix">
<div class="form-group" [hidden]="ldap.ad || directory != directoryType.Ldap"> <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
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} accountName</small> class="form-control"
id="emailPrefixAttribute"
name="EmailPrefixAttribute"
[(ngModel)]="sync.emailPrefixAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} accountName</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="emailSuffix">{{'emailSuffix' | i18n}}</label> <label for="emailSuffix">{{ "emailSuffix" | i18n }}</label>
<input type="text" class="form-control" id="emailSuffix" name="EmailSuffix" <input
[(ngModel)]="sync.emailSuffix"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} @company.com</small> class="form-control"
id="emailSuffix"
name="EmailSuffix"
[(ngModel)]="sync.emailSuffix"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} @company.com</small>
</div> </div>
</div> </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="syncUsers" [(ngModel)]="sync.users" <input
name="SyncUsers"> class="form-check-input"
<label class="form-check-label" for="syncUsers">{{'syncUsers' | i18n}}</label> type="checkbox"
id="syncUsers"
[(ngModel)]="sync.users"
name="SyncUsers"
/>
<label class="form-check-label" for="syncUsers">{{ "syncUsers" | i18n }}</label>
</div> </div>
</div> </div>
<div [hidden]="!sync.users"> <div [hidden]="!sync.users">
<div class="form-group"> <div class="form-group">
<label for="userFilter">{{'userFilter' | i18n}}</label> <label for="userFilter">{{ "userFilter" | i18n }}</label>
<textarea class="form-control" id="userFilter" name="UserFilter" <textarea
[(ngModel)]="sync.userFilter"></textarea> class="form-control"
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}} id="userFilter"
(&amp;(givenName=John)(|(l=Dallas)(l=Austin)))</small> name="UserFilter"
<small class="text-muted form-text" [(ngModel)]="sync.userFilter"
*ngIf="directory === directoryType.AzureActiveDirectory">{{'ex' | i18n}} ></textarea>
exclude:joe@company.com</small> <small class="text-muted form-text" *ngIf="directory === directoryType.Ldap"
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta">{{'ex' | i18n}} >{{ "ex" | i18n }} (&amp;(givenName=John)(|(l=Dallas)(l=Austin)))</small
exclude:joe@company.com | profile.firstName eq "John"</small> >
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite">{{'ex' | i18n}} <small
exclude:joe@company.com | orgName=Engineering</small> class="text-muted form-text"
*ngIf="directory === directoryType.AzureActiveDirectory"
>{{ "ex" | i18n }} exclude:joe@company.com</small
>
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta"
>{{ "ex" | i18n }} exclude:joe@company.com | profile.firstName eq "John"</small
>
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite"
>{{ "ex" | i18n }} exclude:joe@company.com | orgName=Engineering</small
>
</div> </div>
<div class="form-group" [hidden]="directory != directoryType.Ldap"> <div class="form-group" [hidden]="directory != directoryType.Ldap">
<label for="userPath">{{'userPath' | i18n}}</label> <label for="userPath">{{ "userPath" | i18n }}</label>
<input type="text" class="form-control" id="userPath" name="UserPath" <input
[(ngModel)]="sync.userPath"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} CN=Users</small> class="form-control"
id="userPath"
name="UserPath"
[(ngModel)]="sync.userPath"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} CN=Users</small>
</div> </div>
<div [hidden]="directory != directoryType.Ldap || ldap.ad"> <div [hidden]="directory != directoryType.Ldap || ldap.ad">
<div class="form-group"> <div class="form-group">
<label for="userObjectClass">{{'userObjectClass' | i18n}}</label> <label for="userObjectClass">{{ "userObjectClass" | i18n }}</label>
<input type="text" class="form-control" id="userObjectClass" name="UserObjectClass" <input
[(ngModel)]="sync.userObjectClass"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} inetOrgPerson</small> class="form-control"
id="userObjectClass"
name="UserObjectClass"
[(ngModel)]="sync.userObjectClass"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} inetOrgPerson</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="userEmailAttribute">{{'userEmailAttribute' | i18n}}</label> <label for="userEmailAttribute">{{ "userEmailAttribute" | i18n }}</label>
<input type="text" class="form-control" id="userEmailAttribute" name="UserEmailAttribute" <input
[(ngModel)]="sync.userEmailAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} mail</small> class="form-control"
id="userEmailAttribute"
name="UserEmailAttribute"
[(ngModel)]="sync.userEmailAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} mail</small>
</div> </div>
</div> </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="syncGroups" [(ngModel)]="sync.groups" <input
name="SyncGroups"> class="form-check-input"
<label class="form-check-label" for="syncGroups">{{(directory !== directoryType.OneLogin ? type="checkbox"
'syncGroups' : 'syncGroupsOneLogin') | i18n}}</label> id="syncGroups"
[(ngModel)]="sync.groups"
name="SyncGroups"
/>
<label class="form-check-label" for="syncGroups">{{
(directory !== directoryType.OneLogin ? "syncGroups" : "syncGroupsOneLogin") | i18n
}}</label>
</div> </div>
</div> </div>
<div [hidden]="!sync.groups"> <div [hidden]="!sync.groups">
<div class="form-group"> <div class="form-group">
<label for="groupFilter">{{(directory !== directoryType.OneLogin ? 'groupFilter' : <label for="groupFilter">{{
'groupFilterOneLogin') | i18n}}</label> (directory !== directoryType.OneLogin ? "groupFilter" : "groupFilterOneLogin") | i18n
<textarea class="form-control" id="groupFilter" name="GroupFilter" }}</label>
[(ngModel)]="sync.groupFilter"></textarea> <textarea
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}} class="form-control"
(&amp;!(name=Sales*)!(name=IT*))</small> id="groupFilter"
<small class="text-muted form-text" name="GroupFilter"
*ngIf="directory === directoryType.AzureActiveDirectory">{{'ex' | i18n}} [(ngModel)]="sync.groupFilter"
include:Sales,IT</small> ></textarea>
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta">{{'ex' | i18n}} <small class="text-muted form-text" *ngIf="directory === directoryType.Ldap"
include:Sales,IT | type eq "APP_GROUP"</small> >{{ "ex" | i18n }} (&amp;!(name=Sales*)!(name=IT*))</small
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite">{{'ex' | i18n}} >
include:Sales,IT</small> <small
class="text-muted form-text"
*ngIf="directory === directoryType.AzureActiveDirectory"
>{{ "ex" | i18n }} include:Sales,IT</small
>
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta"
>{{ "ex" | i18n }} include:Sales,IT | type eq "APP_GROUP"</small
>
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite"
>{{ "ex" | i18n }} include:Sales,IT</small
>
</div> </div>
<div class="form-group" [hidden]="directory != directoryType.Ldap"> <div class="form-group" [hidden]="directory != directoryType.Ldap">
<label for="groupPath">{{'groupPath' | i18n}}</label> <label for="groupPath">{{ "groupPath" | i18n }}</label>
<input type="text" class="form-control" id="groupPath" name="GroupPath" <input
[(ngModel)]="sync.groupPath"> type="text"
<small class="text-muted form-text" *ngIf="!ldap.ad">{{'ex' | i18n}} CN=Groups</small> class="form-control"
<small class="text-muted form-text" *ngIf="ldap.ad">{{'ex' | i18n}} CN=Users</small> id="groupPath"
name="GroupPath"
[(ngModel)]="sync.groupPath"
/>
<small class="text-muted form-text" *ngIf="!ldap.ad">{{ "ex" | i18n }} CN=Groups</small>
<small class="text-muted form-text" *ngIf="ldap.ad">{{ "ex" | i18n }} CN=Users</small>
</div> </div>
<div [hidden]="directory != directoryType.Ldap || ldap.ad"> <div [hidden]="directory != directoryType.Ldap || ldap.ad">
<div class="form-group"> <div class="form-group">
<label for="groupObjectClass">{{'groupObjectClass' | i18n}}</label> <label for="groupObjectClass">{{ "groupObjectClass" | i18n }}</label>
<input type="text" class="form-control" id="groupObjectClass" name="GroupObjectClass" <input
[(ngModel)]="sync.groupObjectClass"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} groupOfUniqueNames</small> class="form-control"
id="groupObjectClass"
name="GroupObjectClass"
[(ngModel)]="sync.groupObjectClass"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} groupOfUniqueNames</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="groupNameAttribute">{{'groupNameAttribute' | i18n}}</label> <label for="groupNameAttribute">{{ "groupNameAttribute" | i18n }}</label>
<input type="text" class="form-control" id="groupNameAttribute" name="GroupNameAttribute" <input
[(ngModel)]="sync.groupNameAttribute"> type="text"
<small class="text-muted form-text">{{'ex' | i18n}} name</small> class="form-control"
id="groupNameAttribute"
name="GroupNameAttribute"
[(ngModel)]="sync.groupNameAttribute"
/>
<small class="text-muted form-text">{{ "ex" | i18n }} name</small>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,33 +1,27 @@
import { import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { ProfileOrganizationResponse } from 'jslib-common/models/response/profileOrganizationResponse'; import { ProfileOrganizationResponse } from "jslib-common/models/response/profileOrganizationResponse";
import { ConfigurationService } from '../../services/configuration.service'; import { ConfigurationService } from "../../services/configuration.service";
import { DirectoryType } from '../../enums/directoryType'; import { DirectoryType } from "../../enums/directoryType";
import { AzureConfiguration } from '../../models/azureConfiguration'; 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 { OneLoginConfiguration } from "../../models/oneLoginConfiguration";
import { SyncConfiguration } from '../../models/syncConfiguration'; import { SyncConfiguration } from "../../models/syncConfiguration";
import { ConnectorUtils } from '../../utils'; import { ConnectorUtils } from "../../utils";
@Component({ @Component({
selector: 'app-settings', selector: "app-settings",
templateUrl: 'settings.component.html', templateUrl: "settings.component.html",
}) })
export class SettingsComponent implements OnInit, OnDestroy { export class SettingsComponent implements OnInit, OnDestroy {
directory: DirectoryType; directory: DirectoryType;
@@ -44,31 +38,43 @@ export class SettingsComponent implements OnInit, OnDestroy {
showOktaKey: boolean = false; showOktaKey: boolean = false;
showOneLoginSecret: boolean = false; showOneLoginSecret: boolean = false;
constructor(private i18nService: I18nService, private configurationService: ConfigurationService, constructor(
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, private i18nService: I18nService,
private stateService: StateService, private logService: LogService) { private configurationService: ConfigurationService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private stateService: StateService,
private logService: LogService
) {
this.directoryOptions = [ this.directoryOptions = [
{ name: i18nService.t('select'), value: null }, { name: i18nService.t("select"), value: null },
{ name: 'Active Directory / LDAP', value: DirectoryType.Ldap }, { name: "Active Directory / LDAP", value: DirectoryType.Ldap },
{ 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 }, { name: "OneLogin", value: DirectoryType.OneLogin },
]; ];
} }
async ngOnInit() { async ngOnInit() {
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)) ||
this.ldap; this.ldap;
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) || this.gsuite =
(await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite; this.gsuite;
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>( this.azure =
DirectoryType.AzureActiveDirectory)) || this.azure; (await this.configurationService.getDirectory<AzureConfiguration>(
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>( DirectoryType.AzureActiveDirectory
DirectoryType.Okta)) || this.okta; )) || this.azure;
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>( this.okta =
DirectoryType.OneLogin)) || this.oneLogin; (await this.configurationService.getDirectory<OktaConfiguration>(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;
} }
@@ -91,14 +97,14 @@ export class SettingsComponent implements OnInit, OnDestroy {
} }
parseKeyFile() { parseKeyFile() {
const filePicker = (document.getElementById('keyFile') as HTMLInputElement); const filePicker = document.getElementById("keyFile") as HTMLInputElement;
if (filePicker.files == null || filePicker.files.length < 0) { if (filePicker.files == null || filePicker.files.length < 0) {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(filePicker.files[0], 'utf-8'); reader.readAsText(filePicker.files[0], "utf-8");
reader.onload = evt => { reader.onload = (evt) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
try { try {
const result = JSON.parse((evt.target as FileReader).result as string); const result = JSON.parse((evt.target as FileReader).result as string);
@@ -114,14 +120,14 @@ export class SettingsComponent implements OnInit, OnDestroy {
// reset file input // reset file input
// ref: https://stackoverflow.com/a/20552042 // ref: https://stackoverflow.com/a/20552042
filePicker.type = ''; filePicker.type = "";
filePicker.type = 'file'; filePicker.type = "file";
filePicker.value = ''; filePicker.value = "";
}; };
} }
setSslPath(id: string) { setSslPath(id: string) {
const filePicker = (document.getElementById(id + '_file') as HTMLInputElement); const filePicker = document.getElementById(id + "_file") as HTMLInputElement;
if (filePicker.files == null || filePicker.files.length < 0) { if (filePicker.files == null || filePicker.files.length < 0) {
return; return;
} }
@@ -129,28 +135,28 @@ export class SettingsComponent implements OnInit, OnDestroy {
(this.ldap as any)[id] = filePicker.files[0].path; (this.ldap as any)[id] = filePicker.files[0].path;
// reset file input // reset file input
// ref: https://stackoverflow.com/a/20552042 // ref: https://stackoverflow.com/a/20552042
filePicker.type = ''; filePicker.type = "";
filePicker.type = 'file'; filePicker.type = "file";
filePicker.value = ''; filePicker.value = "";
} }
toggleLdapPassword() { toggleLdapPassword() {
this.showLdapPassword = !this.showLdapPassword; this.showLdapPassword = !this.showLdapPassword;
document.getElementById('password').focus(); document.getElementById("password").focus();
} }
toggleAzureKey() { toggleAzureKey() {
this.showAzureKey = !this.showAzureKey; this.showAzureKey = !this.showAzureKey;
document.getElementById('secretKey').focus(); document.getElementById("secretKey").focus();
} }
toggleOktaKey() { toggleOktaKey() {
this.showOktaKey = !this.showOktaKey; this.showOktaKey = !this.showOktaKey;
document.getElementById('oktaToken').focus(); document.getElementById("oktaToken").focus();
} }
toggleOneLoginSecret() { toggleOneLoginSecret() {
this.showOneLoginSecret = !this.showOneLoginSecret; this.showOneLoginSecret = !this.showOneLoginSecret;
document.getElementById('oneLoginClientSecret').focus(); document.getElementById("oneLoginClientSecret").focus();
} }
} }

View File

@@ -3,19 +3,19 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active"> <a class="nav-link" routerLink="dashboard" routerLinkActive="active">
<i class="fa fa-dashboard"></i> <i class="fa fa-dashboard"></i>
{{'dashboard' | i18n}} {{ "dashboard" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="settings" routerLinkActive="active"> <a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="fa fa-cogs"></i> <i class="fa fa-cogs"></i>
{{'settings' | i18n}} {{ "settings" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="more" routerLinkActive="active"> <a class="nav-link" routerLink="more" routerLinkActive="active">
<i class="fa fa-sliders"></i> <i class="fa fa-sliders"></i>
{{'more' | i18n}} {{ "more" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
@Component({ @Component({
selector: 'app-tabs', selector: "app-tabs",
templateUrl: 'tabs.component.html', templateUrl: "tabs.component.html",
}) })
export class TabsComponent {} export class TabsComponent {}

View File

@@ -1,49 +1,49 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { LogLevelType } from 'jslib-common/enums/logLevelType'; import { LogLevelType } from "jslib-common/enums/logLevelType";
import { AuthService } from './services/auth.service'; import { AuthService } from "./services/auth.service";
import { ConfigurationService } from './services/configuration.service'; import { ConfigurationService } from "./services/configuration.service";
import { I18nService } from './services/i18n.service'; import { I18nService } from "./services/i18n.service";
import { KeytarSecureStorageService } from './services/keytarSecureStorage.service'; import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
import { LowdbStorageService } from './services/lowdbStorage.service'; import { LowdbStorageService } from "./services/lowdbStorage.service";
import { NodeApiService } from './services/nodeApi.service'; import { NodeApiService } from "./services/nodeApi.service";
import { SyncService } from './services/sync.service'; import { SyncService } from "./services/sync.service";
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service'; import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service'; import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service'; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { ApiKeyService } from 'jslib-common/services/apiKey.service'; import { ApiKeyService } from "jslib-common/services/apiKey.service";
import { AppIdService } from 'jslib-common/services/appId.service'; import { AppIdService } from "jslib-common/services/appId.service";
import { CipherService } from 'jslib-common/services/cipher.service'; import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from 'jslib-common/services/collection.service'; import { CollectionService } from "jslib-common/services/collection.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { CryptoService } from 'jslib-common/services/crypto.service'; import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from 'jslib-common/services/environment.service'; import { EnvironmentService } from "jslib-common/services/environment.service";
import { FileUploadService } from 'jslib-common/services/fileUpload.service'; import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from 'jslib-common/services/folder.service'; import { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NoopMessagingService } from 'jslib-common/services/noopMessaging.service'; import { NoopMessagingService } from "jslib-common/services/noopMessaging.service";
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from 'jslib-common/services/policy.service'; import { PolicyService } from "jslib-common/services/policy.service";
import { SearchService } from 'jslib-common/services/search.service'; import { SearchService } from "jslib-common/services/search.service";
import { SendService } from 'jslib-common/services/send.service'; import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from 'jslib-common/services/settings.service'; import { SettingsService } from "jslib-common/services/settings.service";
import { SyncService as LoginSyncService } from 'jslib-common/services/sync.service'; import { SyncService as LoginSyncService } from "jslib-common/services/sync.service";
import { TokenService } from 'jslib-common/services/token.service'; import { TokenService } from "jslib-common/services/token.service";
import { UserService } from 'jslib-common/services/user.service'; import { UserService } from "jslib-common/services/user.service";
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { Program } from './program'; import { Program } from "./program";
import { refreshToken } from './services/api.service'; import { refreshToken } from "./services/api.service";
// tslint:disable-next-line // tslint:disable-next-line
const packageJson = require('./package.json'); const packageJson = require("./package.json");
export let searchService: SearchService = null; export let searchService: SearchService = null;
export class Main { export class Main {
@@ -81,74 +81,170 @@ export class Main {
program: Program; program: Program;
constructor() { constructor() {
const applicationName = 'Bitwarden Directory Connector'; const applicationName = "Bitwarden Directory Connector";
if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) { if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR); this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR);
} else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) { } else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) {
this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR); this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR);
} else if (fs.existsSync(path.join(__dirname, 'bitwarden-connector-appdata'))) { } else if (fs.existsSync(path.join(__dirname, "bitwarden-connector-appdata"))) {
this.dataFilePath = path.join(__dirname, 'bitwarden-connector-appdata'); this.dataFilePath = path.join(__dirname, "bitwarden-connector-appdata");
} else if (process.platform === 'darwin') { } else if (process.platform === "darwin") {
this.dataFilePath = path.join(process.env.HOME, 'Library/Application Support/' + applicationName); this.dataFilePath = path.join(
} else if (process.platform === 'win32') { process.env.HOME,
"Library/Application Support/" + applicationName
);
} else if (process.platform === "win32") {
this.dataFilePath = path.join(process.env.APPDATA, applicationName); this.dataFilePath = path.join(process.env.APPDATA, applicationName);
} else if (process.env.XDG_CONFIG_HOME) { } else if (process.env.XDG_CONFIG_HOME) {
this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName); this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName);
} else { } else {
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'; 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(
level => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info); this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== "true" && level <= LogLevelType.Info
);
this.cryptoFunctionService = new NodeCryptoFunctionService(); this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, false, true); this.storageService = new LowdbStorageService(
this.secureStorageService = plaintextSecrets ? this.logService,
this.storageService : new KeytarSecureStorageService(applicationName); null,
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService, this.dataFilePath,
this.cryptoFunctionService, this.platformUtilsService, this.logService); false,
true
);
this.secureStorageService = plaintextSecrets
? this.storageService
: new KeytarSecureStorageService(applicationName);
this.cryptoService = new CryptoService(
this.storageService,
this.secureStorageService,
this.cryptoFunctionService,
this.platformUtilsService,
this.logService
);
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();
this.environmentService = new EnvironmentService(this.storageService); this.environmentService = new EnvironmentService(this.storageService);
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService, this.apiService = new NodeApiService(
() => refreshToken(this.apiKeyService, this.authService), async (expired: boolean) => await this.logout(), this.tokenService,
'Bitwarden_DC/' + this.platformUtilsService.getApplicationVersion() + this.platformUtilsService,
' (' + this.platformUtilsService.getDeviceString().toUpperCase() + ')', (clientId, clientSecret) => this.environmentService,
this.authService.logInApiKey(clientId, clientSecret)); () => refreshToken(this.apiKeyService, this.authService),
async (expired: boolean) => await this.logout(),
"Bitwarden_DC/" +
this.platformUtilsService.getApplicationVersion() +
" (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")",
(clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret)
);
this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService); this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
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.keyConnectorService = new KeyConnectorService(this.storageService, this.userService, this.cryptoService, this.keyConnectorService = new KeyConnectorService(
this.apiService, this.tokenService, this.logService); this.storageService,
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService, this.userService,
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null, this.cryptoService,
this.logService, this.apiKeyService, this.cryptoFunctionService, this.environmentService, this.keyConnectorService); this.apiService,
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService, this.tokenService,
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true'); this.logService
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService, );
this.apiService, this.messagingService, this.i18nService, this.environmentService); this.authService = new AuthService(
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, null); this.cryptoService,
this.apiService,
this.userService,
this.tokenService,
this.appIdService,
this.i18nService,
this.platformUtilsService,
this.messagingService,
null,
this.logService,
this.apiKeyService,
this.cryptoFunctionService,
this.environmentService,
this.keyConnectorService
);
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.apiService,
this.messagingService,
this.i18nService,
this.environmentService
);
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.storageService,
null
);
this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); this.policyService = new PolicyService(this.userService, this.storageService, this.apiService);
this.settingsService = new SettingsService(this.userService, this.storageService); this.settingsService = new SettingsService(this.userService, this.storageService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, this.cipherService = new CipherService(
this.apiService, this.fileUploadService, this.storageService, this.i18nService, () => searchService, this.cryptoService,
this.logService); this.userService,
this.settingsService,
this.apiService,
this.fileUploadService,
this.storageService,
this.i18nService,
() => searchService,
this.logService
);
this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService);
this.folderService = new FolderService(this.cryptoService, this.userService, this.apiService, this.folderService = new FolderService(
this.storageService, this.i18nService, this.cipherService); this.cryptoService,
this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.userService,
this.i18nService); this.apiService,
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService, this.storageService, this.storageService,
this.i18nService, this.cryptoFunctionService); this.i18nService,
this.cipherService
);
this.collectionService = new CollectionService(
this.cryptoService,
this.userService,
this.storageService,
this.i18nService
);
this.sendService = new SendService(
this.cryptoService,
this.userService,
this.apiService,
this.fileUploadService,
this.storageService,
this.i18nService,
this.cryptoFunctionService
);
this.loginSyncService = new LoginSyncService(this.userService, this.apiService, this.settingsService, this.loginSyncService = new LoginSyncService(
this.folderService, this.cipherService, this.cryptoService, this.collectionService, this.storageService, this.userService,
this.messagingService, this.policyService, this.sendService, this.logService, this.tokenService, this.apiService,
this.keyConnectorService, async (expired: boolean) => this.messagingService.send('logout', { expired: expired })); this.settingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.storageService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.tokenService,
this.keyConnectorService,
async (expired: boolean) => this.messagingService.send("logout", { expired: expired })
);
this.program = new Program(this); this.program = new Program(this);
} }
@@ -177,7 +273,9 @@ export class Main {
await this.i18nService.init(locale); await this.i18nService.init(locale);
this.authService.init(); this.authService.init();
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey); const installedVersion = await this.storageService.get<string>(
ConstantsService.installedVersionKey
);
const currentVersion = await this.platformUtilsService.getApplicationVersion(); const currentVersion = await this.platformUtilsService.getApplicationVersion();
if (installedVersion == null || installedVersion !== currentVersion) { if (installedVersion == null || installedVersion !== currentVersion) {
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion); await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);

View File

@@ -1,19 +1,22 @@
import * as program from 'commander'; import * as program from "commander";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ConfigurationService } from '../services/configuration.service'; import { ConfigurationService } from "../services/configuration.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
export class ClearCacheCommand { export class ClearCacheCommand {
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { } constructor(
private configurationService: ConfigurationService,
private i18nService: I18nService
) {}
async run(cmd: program.OptionValues): Promise<Response> { async run(cmd: program.OptionValues): Promise<Response> {
try { try {
await this.configurationService.clearStatefulSettings(true); await this.configurationService.clearStatefulSettings(true);
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null); const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);

View File

@@ -1,25 +1,25 @@
import * as program from 'commander'; import * as program from "commander";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ConfigurationService } from '../services/configuration.service'; import { ConfigurationService } from "../services/configuration.service";
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { AzureConfiguration } from '../models/azureConfiguration'; 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 { OneLoginConfiguration } from "../models/oneLoginConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { ConnectorUtils } from '../utils'; import { ConnectorUtils } from "../utils";
import { NodeUtils } from 'jslib-common/misc/nodeUtils'; import { NodeUtils } from "jslib-common/misc/nodeUtils";
export class ConfigCommand { export class ConfigCommand {
private directory: DirectoryType; private directory: DirectoryType;
@@ -30,12 +30,15 @@ export class ConfigCommand {
private oneLogin = new OneLoginConfiguration(); private oneLogin = new OneLoginConfiguration();
private sync = new SyncConfiguration(); private sync = new SyncConfiguration();
constructor(private environmentService: EnvironmentService, private i18nService: I18nService, constructor(
private configurationService: ConfigurationService) { } private environmentService: EnvironmentService,
private i18nService: I18nService,
private configurationService: ConfigurationService
) {}
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> { async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase(); setting = setting.toLowerCase();
if (value == null || value === '') { if (value == null || value === "") {
if (options.secretfile) { if (options.secretfile) {
value = await NodeUtils.readFirstLine(options.secretfile); value = await NodeUtils.readFirstLine(options.secretfile);
} else if (options.secretenv && process.env[options.secretenv]) { } else if (options.secretenv && process.env[options.secretenv]) {
@@ -44,39 +47,39 @@ export class ConfigCommand {
} }
try { try {
switch (setting) { switch (setting) {
case 'server': case "server":
await this.setServer(value); await this.setServer(value);
break; break;
case 'directory': case "directory":
await this.setDirectory(value); await this.setDirectory(value);
break; break;
case 'ldap.password': case "ldap.password":
await this.setLdapPassword(value); await this.setLdapPassword(value);
break; break;
case 'gsuite.key': case "gsuite.key":
await this.setGSuiteKey(value); await this.setGSuiteKey(value);
break; break;
case 'azure.key': case "azure.key":
await this.setAzureKey(value); await this.setAzureKey(value);
break; break;
case 'okta.token': case "okta.token":
await this.setOktaToken(value); await this.setOktaToken(value);
break; break;
case 'onelogin.secret': case "onelogin.secret":
await this.setOneLoginSecret(value); await this.setOneLoginSecret(value);
break; break;
default: default:
return Response.badRequest('Unknown setting.'); return Response.badRequest("Unknown setting.");
} }
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
} }
const res = new MessageResponse(this.i18nService.t('savedSetting', setting), null); const res = new MessageResponse(this.i18nService.t("savedSetting", setting), null);
return Response.success(res); return Response.success(res);
} }
private async setServer(url: string) { private async setServer(url: string) {
url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url); url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
await this.environmentService.setUrls({ await this.environmentService.setUrls({
base: url, base: url,
}); });
@@ -85,7 +88,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.OneLogin) { 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();
this.directory = dir; this.directory = dir;
@@ -124,16 +127,23 @@ export class ConfigCommand {
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)) ||
this.ldap; this.ldap;
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) || this.gsuite =
(await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite; this.gsuite;
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>( this.azure =
DirectoryType.AzureActiveDirectory)) || this.azure; (await this.configurationService.getDirectory<AzureConfiguration>(
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>( DirectoryType.AzureActiveDirectory
DirectoryType.Okta)) || this.okta; )) || this.azure;
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>( this.okta =
DirectoryType.OneLogin)) || this.oneLogin; (await this.configurationService.getDirectory<OktaConfiguration>(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;
} }

View File

@@ -1,9 +1,9 @@
import * as program from 'commander'; import * as program from "commander";
import { ConfigurationService } from '../services/configuration.service'; import { ConfigurationService } from "../services/configuration.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class LastSyncCommand { export class LastSyncCommand {
constructor(private configurationService: ConfigurationService) {} constructor(private configurationService: ConfigurationService) {}
@@ -11,16 +11,18 @@ export class LastSyncCommand {
async run(object: string): Promise<Response> { async run(object: string): Promise<Response> {
try { try {
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'groups': case "groups":
const groupsDate = await this.configurationService.getLastGroupSyncDate(); const groupsDate = await this.configurationService.getLastGroupSyncDate();
const groupsRes = new StringResponse(groupsDate == null ? null : groupsDate.toISOString()); const groupsRes = new StringResponse(
groupsDate == null ? null : groupsDate.toISOString()
);
return Response.success(groupsRes); return Response.success(groupsRes);
case 'users': case "users":
const usersDate = await this.configurationService.getLastUserSyncDate(); const usersDate = await this.configurationService.getLastUserSyncDate();
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString()); const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
return Response.success(usersRes); return Response.success(usersRes);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);

View File

@@ -1,9 +1,9 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { SyncService } from '../services/sync.service'; import { SyncService } from "../services/sync.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
export class SyncCommand { export class SyncCommand {
constructor(private syncService: SyncService, private i18nService: I18nService) {} constructor(private syncService: SyncService, private i18nService: I18nService) {}
@@ -13,8 +13,10 @@ export class SyncCommand {
const result = await this.syncService.sync(false, false); const result = await this.syncService.sync(false, false);
const groupCount = result[0] != null ? result[0].length : 0; const groupCount = result[0] != null ? result[0].length : 0;
const userCount = result[1] != null ? result[1].length : 0; const userCount = result[1] != null ? result[1].length : 0;
const res = new MessageResponse(this.i18nService.t('syncingComplete'), const res = new MessageResponse(
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString())); this.i18nService.t("syncingComplete"),
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);

View File

@@ -1,20 +1,24 @@
import * as program from 'commander'; import * as program from "commander";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { SyncService } from '../services/sync.service'; import { SyncService } from "../services/sync.service";
import { ConnectorUtils } from '../utils'; import { ConnectorUtils } from "../utils";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { TestResponse } from '../models/response/testResponse'; import { TestResponse } from "../models/response/testResponse";
export class TestCommand { export class TestCommand {
constructor(private syncService: SyncService, private i18nService: I18nService) {} constructor(private syncService: SyncService, private i18nService: I18nService) {}
async run(cmd: program.OptionValues): Promise<Response> { async run(cmd: program.OptionValues): Promise<Response> {
try { try {
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false); const result = await ConnectorUtils.simulate(
this.syncService,
this.i18nService,
cmd.last || false
);
const res = new TestResponse(result); const res = new TestResponse(result);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {

2
src/global.d.ts vendored
View File

@@ -1,3 +1,3 @@
declare function escape(s: string): string; declare function escape(s: string): string;
declare function unescape(s: string): string; declare function unescape(s: string): string;
declare module 'duo_web_sdk'; declare module "duo_web_sdk";

View File

@@ -1,12 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; <meta
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"> http-equiv="Content-Security-Policy"
<meta name="viewport" content="width=device-width, initial-scale=1"> content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden Directory Connector</title> <title>Bitwarden Directory Connector</title>
<base href=""> <base href="" />
</head> </head>
<body> <body>
<app-root> <app-root>

View File

@@ -1,17 +1,17 @@
import { app } from 'electron'; import { app } from "electron";
import * as path from 'path'; import * as path from "path";
import { MenuMain } from './main/menu.main'; import { MenuMain } from "./main/menu.main";
import { MessagingMain } from './main/messaging.main'; import { MessagingMain } from "./main/messaging.main";
import { I18nService } from './services/i18n.service'; import { I18nService } from "./services/i18n.service";
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener'; import { KeytarStorageListener } from "jslib-electron/keytarStorageListener";
import { ElectronLogService } from 'jslib-electron/services/electronLog.service'; import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service'; import { ElectronMainMessagingService } from "jslib-electron/services/electronMainMessaging.service";
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service'; import { ElectronStorageService } from "jslib-electron/services/electronStorage.service";
import { TrayMain } from 'jslib-electron/tray.main'; import { TrayMain } from "jslib-electron/tray.main";
import { UpdaterMain } from 'jslib-electron/updater.main'; import { UpdaterMain } from "jslib-electron/updater.main";
import { WindowMain } from 'jslib-electron/window.main'; import { WindowMain } from "jslib-electron/window.main";
export class Main { export class Main {
logService: ElectronLogService; logService: ElectronLogService;
@@ -31,76 +31,102 @@ export class Main {
let appDataPath = null; let appDataPath = null;
if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) { if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) {
appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR; appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR;
} else if (process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null) { } else if (process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null) {
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'bitwarden-connector-appdata'); appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, "bitwarden-connector-appdata");
} }
if (appDataPath != null) { if (appDataPath != null) {
app.setPath('userData', appDataPath); app.setPath("userData", appDataPath);
} }
app.setPath('logs', path.join(app.getPath('userData'), 'logs')); app.setPath("logs", path.join(app.getPath("userData"), "logs"));
const args = process.argv.slice(1); const args = process.argv.slice(1);
const watch = args.some(val => val === '--watch'); const watch = args.some((val) => val === "--watch");
if (watch) { if (watch) {
// tslint:disable-next-line // tslint:disable-next-line
require('electron-reload')(__dirname, {}); require("electron-reload")(__dirname, {});
} }
this.logService = new ElectronLogService(null, app.getPath('userData')); this.logService = new ElectronLogService(null, app.getPath("userData"));
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, this.logService, false, 800, 600, arg => this.processDeepLink(arg), null); this.windowMain = new WindowMain(
this.storageService,
this.logService,
false,
800,
600,
(arg) => this.processDeepLink(arg),
null
);
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.messagingService.send('checkingForUpdate'); this.i18nService,
}, () => { this.windowMain,
this.messagingService.send('doneCheckingForUpdate'); "directory-connector",
}, () => { () => {
this.messagingService.send('doneCheckingForUpdate'); this.messagingService.send("checkingForUpdate");
}, 'bitwardenDirectoryConnector'); },
() => {
this.messagingService.send("doneCheckingForUpdate");
},
() => {
this.messagingService.send("doneCheckingForUpdate");
},
"bitwardenDirectoryConnector"
);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService); this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
this.messagingMain = new MessagingMain(this.windowMain, this.menuMain, this.updaterMain, this.trayMain); this.messagingMain = new MessagingMain(
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => { this.windowMain,
this.menuMain,
this.updaterMain,
this.trayMain
);
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
this.messagingMain.onMessage(message); this.messagingMain.onMessage(message);
}); });
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null); this.keytarStorageListener = new KeytarStorageListener("Bitwarden Directory Connector", null);
} }
bootstrap() { bootstrap() {
this.keytarStorageListener.init(); this.keytarStorageListener.init();
this.windowMain.init().then(async () => { this.windowMain.init().then(
async () => {
await this.i18nService.init(app.getLocale()); await this.i18nService.init(app.getLocale());
this.menuMain.init(); this.menuMain.init();
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')) { if (!app.isDefaultProtocolClient("bwdc")) {
app.setAsDefaultProtocolClient('bwdc'); app.setAsDefaultProtocolClient("bwdc");
} }
// Process protocol for macOS // Process protocol for macOS
app.on('open-url', (event, url) => { app.on("open-url", (event, url) => {
event.preventDefault(); event.preventDefault();
this.processDeepLink([url]); 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 { private processDeepLink(argv: string[]): void {
argv.filter(s => s.indexOf('bwdc://') === 0).forEach(s => { argv
.filter((s) => s.indexOf("bwdc://") === 0)
.forEach((s) => {
const url = new URL(s); const url = new URL(s);
const code = url.searchParams.get('code'); const code = url.searchParams.get("code");
const receivedState = url.searchParams.get('state'); const receivedState = url.searchParams.get("state");
if (code != null && receivedState != null) { if (code != null && receivedState != null) {
this.messagingService.send('ssoCallback', { code: code, state: receivedState }); this.messagingService.send("ssoCallback", { code: code, state: receivedState });
} }
}); });
} }

View File

@@ -1,12 +1,8 @@
import { import { Menu, MenuItem, MenuItemConstructorOptions } from "electron";
Menu,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { Main } from '../main'; import { Main } from "../main";
import { BaseMenu } from 'jslib-electron/baseMenu'; import { BaseMenu } from "jslib-electron/baseMenu";
export class MenuMain extends BaseMenu { export class MenuMain extends BaseMenu {
menu: Menu; menu: Menu;
@@ -25,22 +21,22 @@ export class MenuMain extends BaseMenu {
const template: MenuItemConstructorOptions[] = [ const template: MenuItemConstructorOptions[] = [
this.editMenuItemOptions, this.editMenuItemOptions,
{ {
label: this.i18nService.t('view'), label: this.i18nService.t("view"),
submenu: this.viewSubMenuItemOptions, submenu: this.viewSubMenuItemOptions,
}, },
this.windowMenuItemOptions, this.windowMenuItemOptions,
]; ];
if (process.platform === 'darwin') { if (process.platform === "darwin") {
const firstMenuPart: MenuItemConstructorOptions[] = [ const firstMenuPart: MenuItemConstructorOptions[] = [
{ {
label: this.i18nService.t('aboutBitwarden'), label: this.i18nService.t("aboutBitwarden"),
role: 'about', role: "about",
}, },
]; ];
template.unshift({ template.unshift({
label: this.main.i18nService.t('bitwardenDirectoryConnector'), label: this.main.i18nService.t("bitwardenDirectoryConnector"),
submenu: firstMenuPart.concat(this.macAppMenuItemOptions), submenu: firstMenuPart.concat(this.macAppMenuItemOptions),
}); });
@@ -48,19 +44,24 @@ export class MenuMain extends BaseMenu {
template[template.length - 1].submenu = this.macWindowSubmenuOptions; template[template.length - 1].submenu = this.macWindowSubmenuOptions;
} }
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(1, 0, (template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(
1,
0,
{ {
label: this.main.i18nService.t(process.platform === 'darwin' ? 'hideToMenuBar' : 'hideToTray'), label: this.main.i18nService.t(
click: () => this.main.messagingService.send('hideToTray'), process.platform === "darwin" ? "hideToMenuBar" : "hideToTray"
accelerator: 'CmdOrCtrl+Shift+M', ),
click: () => this.main.messagingService.send("hideToTray"),
accelerator: "CmdOrCtrl+Shift+M",
}, },
{ {
type: 'checkbox', type: "checkbox",
label: this.main.i18nService.t('alwaysOnTop'), label: this.main.i18nService.t("alwaysOnTop"),
checked: this.windowMain.win.isAlwaysOnTop(), checked: this.windowMain.win.isAlwaysOnTop(),
click: () => this.main.windowMain.toggleAlwaysOnTop(), click: () => this.main.windowMain.toggleAlwaysOnTop(),
accelerator: 'CmdOrCtrl+Shift+T', accelerator: "CmdOrCtrl+Shift+T",
}); }
);
this.menu = Menu.buildFromTemplate(template); this.menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(this.menu); Menu.setApplicationMenu(this.menu);

View File

@@ -1,43 +1,44 @@
import { import { app, ipcMain } from "electron";
app,
ipcMain,
} from 'electron';
import { TrayMain } from 'jslib-electron/tray.main'; import { TrayMain } from "jslib-electron/tray.main";
import { UpdaterMain } from 'jslib-electron/updater.main'; import { UpdaterMain } from "jslib-electron/updater.main";
import { WindowMain } from 'jslib-electron/window.main'; import { WindowMain } from "jslib-electron/window.main";
import { MenuMain } from './menu.main'; import { MenuMain } from "./menu.main";
const SyncCheckInterval = 60 * 1000; // 1 minute const SyncCheckInterval = 60 * 1000; // 1 minute
export class MessagingMain { export class MessagingMain {
private syncTimeout: NodeJS.Timer; private syncTimeout: NodeJS.Timer;
constructor(private windowMain: WindowMain, private menuMain: MenuMain, constructor(
private updaterMain: UpdaterMain, private trayMain: TrayMain) { } private windowMain: WindowMain,
private menuMain: MenuMain,
private updaterMain: UpdaterMain,
private trayMain: TrayMain
) {}
init() { init() {
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message)); ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message));
} }
onMessage(message: any) { onMessage(message: any) {
switch (message.command) { switch (message.command) {
case 'checkForUpdate': case "checkForUpdate":
this.updaterMain.checkForUpdate(true); this.updaterMain.checkForUpdate(true);
break; break;
case 'scheduleNextDirSync': case "scheduleNextDirSync":
this.scheduleNextSync(); this.scheduleNextSync();
break; break;
case 'cancelDirSync': case "cancelDirSync":
this.windowMain.win.webContents.send('messagingService', { this.windowMain.win.webContents.send("messagingService", {
command: 'syncScheduleStopped', command: "syncScheduleStopped",
}); });
if (this.syncTimeout) { if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout); global.clearTimeout(this.syncTimeout);
} }
break; break;
case 'hideToTray': case "hideToTray":
this.trayMain.hideToTray(); this.trayMain.hideToTray();
break; break;
default: default:
@@ -46,8 +47,8 @@ export class MessagingMain {
} }
private scheduleNextSync() { private scheduleNextSync() {
this.windowMain.win.webContents.send('messagingService', { this.windowMain.win.webContents.send("messagingService", {
command: 'syncScheduleStarted', command: "syncScheduleStarted",
}); });
if (this.syncTimeout) { if (this.syncTimeout) {
@@ -59,8 +60,8 @@ export class MessagingMain {
return; return;
} }
this.windowMain.win.webContents.send('messagingService', { this.windowMain.win.webContents.send("messagingService", {
command: 'checkDirSync', command: "checkDirSync",
}); });
}, SyncCheckInterval); }, SyncCheckInterval);
} }

View File

@@ -1,4 +1,4 @@
import { Entry } from './entry'; import { Entry } from "./entry";
export class GroupEntry extends Entry { export class GroupEntry extends Entry {
name: string; name: string;

View File

@@ -1,5 +1,5 @@
export class OneLoginConfiguration { export class OneLoginConfiguration {
clientId: string; clientId: string;
clientSecret: string; clientSecret: string;
region = 'us'; region = "us";
} }

View File

@@ -1,4 +1,4 @@
import { GroupEntry } from '../groupEntry'; import { GroupEntry } from "../groupEntry";
export class GroupResponse { export class GroupResponse {
externalId: string; externalId: string;

View File

@@ -1,9 +1,9 @@
import { GroupResponse } from './groupResponse'; import { GroupResponse } from "./groupResponse";
import { UserResponse } from './userResponse'; import { UserResponse } from "./userResponse";
import { SimResult } from '../simResult'; import { SimResult } from "../simResult";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
export class TestResponse implements BaseResponse { export class TestResponse implements BaseResponse {
object: string; object: string;
@@ -13,10 +13,13 @@ export class TestResponse implements BaseResponse {
deletedUsers: UserResponse[] = []; deletedUsers: UserResponse[] = [];
constructor(result: SimResult) { constructor(result: SimResult) {
this.object = 'test'; this.object = "test";
this.groups = result.groups != null ? result.groups.map(g => new GroupResponse(g)) : []; this.groups = result.groups != null ? result.groups.map((g) => new GroupResponse(g)) : [];
this.enabledUsers = result.enabledUsers != null ? result.enabledUsers.map(u => new UserResponse(u)) : []; this.enabledUsers =
this.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map(u => new UserResponse(u)) : []; result.enabledUsers != null ? result.enabledUsers.map((u) => new UserResponse(u)) : [];
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map(u => new UserResponse(u)) : []; this.disabledUsers =
result.disabledUsers != null ? result.disabledUsers.map((u) => new UserResponse(u)) : [];
this.deletedUsers =
result.deletedUsers != null ? result.deletedUsers.map((u) => new UserResponse(u)) : [];
} }
} }

View File

@@ -1,4 +1,4 @@
import { UserEntry } from '../userEntry'; import { UserEntry } from "../userEntry";
export class UserResponse { export class UserResponse {
externalId: string; externalId: string;

View File

@@ -1,5 +1,5 @@
import { GroupEntry } from './groupEntry'; import { GroupEntry } from "./groupEntry";
import { UserEntry } from './userEntry'; import { UserEntry } from "./userEntry";
export class SimResult { export class SimResult {
groups: GroupEntry[] = []; groups: GroupEntry[] = [];

View File

@@ -1,4 +1,4 @@
import { Entry } from './entry'; import { Entry } from "./entry";
export class UserEntry extends Entry { export class UserEntry extends Entry {
email: string; email: string;

View File

@@ -1,33 +1,33 @@
import * as chalk from 'chalk'; import * as chalk from "chalk";
import * as program from 'commander'; import * as program from "commander";
import * as path from 'path'; import * as path from "path";
import { Main } from './bwdc'; import { Main } from "./bwdc";
import { ClearCacheCommand } from './commands/clearCache.command'; import { ClearCacheCommand } from "./commands/clearCache.command";
import { ConfigCommand } from './commands/config.command'; import { ConfigCommand } from "./commands/config.command";
import { LastSyncCommand } from './commands/lastSync.command'; import { LastSyncCommand } from "./commands/lastSync.command";
import { SyncCommand } from './commands/sync.command'; import { SyncCommand } from "./commands/sync.command";
import { TestCommand } from './commands/test.command'; import { TestCommand } from "./commands/test.command";
import { LoginCommand } from 'jslib-node/cli/commands/login.command'; import { LoginCommand } from "jslib-node/cli/commands/login.command";
import { LogoutCommand } from 'jslib-node/cli/commands/logout.command'; import { LogoutCommand } from "jslib-node/cli/commands/logout.command";
import { UpdateCommand } from 'jslib-node/cli/commands/update.command'; import { UpdateCommand } from "jslib-node/cli/commands/update.command";
import { BaseProgram } from 'jslib-node/cli/baseProgram'; import { BaseProgram } from "jslib-node/cli/baseProgram";
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => { const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
const stream = error ? process.stderr : process.stdout; const stream = error ? process.stderr : process.stdout;
if (finalLine && process.platform === 'win32') { if (finalLine && process.platform === "win32") {
stream.write(s); stream.write(s);
} else { } else {
stream.write(s + '\n'); stream.write(s + "\n");
} }
}; };
@@ -41,68 +41,79 @@ export class Program extends BaseProgram {
async run() { async run() {
program program
.option('--pretty', 'Format output. JSON is tabbed with two spaces.') .option("--pretty", "Format output. JSON is tabbed with two spaces.")
.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('--cleanexit', 'Exit with a success exit code (0) unless an error is thrown.') .option("--cleanexit", "Exit with a success exit code (0) unless an error is thrown.")
.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.') .option("--nointeraction", "Do not prompt for interactive user input.")
.version(await this.main.platformUtilsService.getApplicationVersion(), '-v, --version'); .version(await this.main.platformUtilsService.getApplicationVersion(), "-v, --version");
program.on('option:pretty', () => { program.on("option:pretty", () => {
process.env.BW_PRETTY = 'true'; process.env.BW_PRETTY = "true";
}); });
program.on('option:raw', () => { program.on("option:raw", () => {
process.env.BW_RAW = 'true'; process.env.BW_RAW = "true";
}); });
program.on('option:quiet', () => { program.on("option:quiet", () => {
process.env.BW_QUIET = 'true'; process.env.BW_QUIET = "true";
}); });
program.on('option:response', () => { program.on("option:response", () => {
process.env.BW_RESPONSE = 'true'; process.env.BW_RESPONSE = "true";
}); });
program.on('option:cleanexit', () => { program.on("option:cleanexit", () => {
process.env.BW_CLEANEXIT = 'true'; process.env.BW_CLEANEXIT = "true";
}); });
program.on('option:nointeraction', () => { program.on("option:nointeraction", () => {
process.env.BW_NOINTERACTION = 'true'; 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);
process.exitCode = 1; process.exitCode = 1;
}); });
program.on('--help', () => { program.on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc login'); writeLn(" bwdc login");
writeLn(' bwdc test'); writeLn(" bwdc test");
writeLn(' bwdc sync'); writeLn(" bwdc sync");
writeLn(' bwdc last-sync'); writeLn(" bwdc last-sync");
writeLn(' bwdc config server https://bw.company.com'); writeLn(" bwdc config server https://bw.company.com");
writeLn(' bwdc update'); writeLn(" bwdc update");
writeLn('', true); writeLn("", true);
}); });
program program
.command('login [clientId] [clientSecret]') .command("login [clientId] [clientSecret]")
.description('Log into an organization account.', { .description("Log into an organization account.", {
clientId: 'Client_id part of your organization\'s API key', clientId: "Client_id part of your organization's API key",
clientSecret: 'Client_secret part of your organization\'s API key', clientSecret: "Client_secret part of your organization's API key",
}) })
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => { .action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
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.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService, this.main.authService,
this.main.platformUtilsService, this.main.userService, this.main.cryptoService, this.main.apiService,
this.main.policyService, 'connector', this.main.loginSyncService, this.main.keyConnectorService); this.main.i18nService,
this.main.environmentService,
this.main.passwordGenerationService,
this.main.cryptoFunctionService,
this.main.platformUtilsService,
this.main.userService,
this.main.cryptoService,
this.main.policyService,
"connector",
this.main.loginSyncService,
this.main.keyConnectorService
);
if (!Utils.isNullOrWhitespace(clientId)) { if (!Utils.isNullOrWhitespace(clientId)) {
process.env.BW_CLIENTID = clientId; process.env.BW_CLIENTID = clientId;
@@ -117,32 +128,35 @@ export class Program extends BaseProgram {
}); });
program program
.command('logout') .command("logout")
.description('Log out of the current user account.') .description("Log out of the current user account.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc logout'); writeLn(" bwdc logout");
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const command = new LogoutCommand(this.main.authService, this.main.i18nService, const command = new LogoutCommand(
async () => await this.main.logout()); this.main.authService,
this.main.i18nService,
async () => await this.main.logout()
);
const response = await command.run(); const response = await command.run();
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('test') .command("test")
.description('Test a simulated sync.') .description("Test a simulated sync.")
.option('-l, --last', 'Since the last successful sync.') .option("-l, --last", "Since the last successful sync.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc test'); writeLn(" bwdc test");
writeLn(' bwdc test --last'); writeLn(" bwdc test --last");
writeLn('', true); writeLn("", true);
}) })
.action(async (options: program.OptionValues) => { .action(async (options: program.OptionValues) => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
@@ -152,13 +166,13 @@ export class Program extends BaseProgram {
}); });
program program
.command('sync') .command("sync")
.description('Sync the directory.') .description("Sync the directory.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc sync'); writeLn(" bwdc sync");
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
@@ -168,18 +182,18 @@ export class Program extends BaseProgram {
}); });
program program
.command('last-sync <object>') .command("last-sync <object>")
.description('Get the last successful sync date.') .description("Get the last successful sync date.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Returns empty response if no sync has been performed for the given object.'); writeLn(" Returns empty response if no sync has been performed for the given object.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc last-sync groups'); writeLn(" bwdc last-sync groups");
writeLn(' bwdc last-sync users'); writeLn(" bwdc last-sync users");
writeLn('', true); writeLn("", true);
}) })
.action(async (object: string) => { .action(async (object: string) => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
@@ -189,95 +203,106 @@ export class Program extends BaseProgram {
}); });
program program
.command('config <setting> [value]') .command("config <setting> [value]")
.description('Configure settings.') .description("Configure settings.")
.option('--secretenv <variable-name>', 'Read secret from the named environment variable.') .option("--secretenv <variable-name>", "Read secret from the named environment variable.")
.option('--secretfile <filename>', 'Read secret from first line of the named file.') .option("--secretfile <filename>", "Read secret from first line of the named file.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Settings:'); writeLn("\n Settings:");
writeLn(''); writeLn("");
writeLn(' server - On-premise hosted installation URL.'); writeLn(" server - On-premise hosted installation URL.");
writeLn(' directory - The type of directory to use.'); writeLn(" directory - The type of directory to use.");
writeLn(' ldap.password - The password for connection to this LDAP server.'); writeLn(" ldap.password - The password for connection to this LDAP server.");
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(" onelogin.secret - The OneLogin client secret.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc config server https://bw.company.com'); writeLn(" bwdc config server https://bw.company.com");
writeLn(' bwdc config server bitwarden.com'); writeLn(" bwdc config server bitwarden.com");
writeLn(' bwdc config directory 1'); writeLn(" bwdc config directory 1");
writeLn(' bwdc config ldap.password <password>'); writeLn(" bwdc config ldap.password <password>");
writeLn(' bwdc config ldap.password --secretenv LDAP_PWD'); writeLn(" bwdc config ldap.password --secretenv LDAP_PWD");
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(" bwdc config onelogin.secret <secret>");
writeLn('', true); writeLn("", true);
}) })
.action(async (setting: string, value: string, options: program.OptionValues) => { .action(async (setting: string, value: string, options: program.OptionValues) => {
const command = new ConfigCommand(this.main.environmentService, this.main.i18nService, const command = new ConfigCommand(
this.main.configurationService); this.main.environmentService,
this.main.i18nService,
this.main.configurationService
);
const response = await command.run(setting, value, options); const response = await command.run(setting, value, options);
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('data-file') .command("data-file")
.description('Path to data.json database file.') .description("Path to data.json database file.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc data-file'); writeLn(" bwdc data-file");
writeLn('', true); writeLn("", true);
}) })
.action(() => { .action(() => {
this.processResponse( this.processResponse(
Response.success(new StringResponse(path.join(this.main.dataFilePath, 'data.json')))); Response.success(new StringResponse(path.join(this.main.dataFilePath, "data.json")))
);
}); });
program program
.command('clear-cache') .command("clear-cache")
.description('Clear the sync cache.') .description("Clear the sync cache.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc clear-cache'); writeLn(" bwdc clear-cache");
writeLn('', true); writeLn("", true);
}) })
.action(async (options: program.OptionValues) => { .action(async (options: program.OptionValues) => {
const command = new ClearCacheCommand(this.main.configurationService, this.main.i18nService); const command = new ClearCacheCommand(
this.main.configurationService,
this.main.i18nService
);
const response = await command.run(options); const response = await command.run(options);
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('update') .command("update")
.description('Check for updates.') .description("Check for updates.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Returns the URL to download the newest version of this CLI tool.'); writeLn(" Returns the URL to download the newest version of this CLI tool.");
writeLn(''); writeLn("");
writeLn(' Use the `--raw` option to return only the download URL for the update.'); writeLn(" Use the `--raw` option to return only the download URL for the update.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bwdc update'); writeLn(" bwdc update");
writeLn(' bwdc update --raw'); writeLn(" bwdc update --raw");
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService, const command = new UpdateCommand(
'directory-connector', 'bwdc', false); this.main.platformUtilsService,
this.main.i18nService,
"directory-connector",
"bwdc",
false
);
const response = await command.run(); const response = await command.run();
this.processResponse(response); this.processResponse(response);
}); });
program program.parse(process.argv);
.parse(process.argv);
if (process.argv.slice(2).length === 0) { if (process.argv.slice(2).length === 0) {
program.outputHelp(); program.outputHelp();
@@ -289,14 +314,17 @@ export class Program extends BaseProgram {
if (authed) { if (authed) {
const type = await this.apiKeyService.getEntityType(); const type = await this.apiKeyService.getEntityType();
const id = await this.apiKeyService.getEntityId(); const id = await this.apiKeyService.getEntityId();
this.processResponse(Response.error('You are already logged in as ' + type + '.' + id + '.'), true); this.processResponse(
Response.error("You are already logged in as " + type + "." + id + "."),
true
);
} }
} }
async exitIfNotAuthed() { async exitIfNotAuthed() {
const authed = await this.apiKeyService.isAuthenticated(); const authed = await this.apiKeyService.isAuthenticated();
if (!authed) { if (!authed) {
this.processResponse(Response.error('You are not logged in.'), true); this.processResponse(Response.error("You are not logged in."), true);
} }
} }
} }

View File

@@ -1,5 +1,15 @@
$theme-colors: ( "primary": #175DDC, "primary-accent": #1252A3, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16, "secondary": #ced4da, "secondary-alt": #1A3B66); $theme-colors: (
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; "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";
$h1-font-size: 2rem; $h1-font-size: 2rem;
$h2-font-size: 1.3rem; $h2-font-size: 1.3rem;
@@ -8,13 +18,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: map_get($theme-colors, "primary");
$primary-accent: map_get($theme-colors, 'primary-accent'); $primary-accent: map_get($theme-colors, "primary-accent");
$success: map_get($theme-colors, 'success'); $success: map_get($theme-colors, "success");
$info: map_get($theme-colors, 'info'); $info: map_get($theme-colors, "info");
$warning: map_get($theme-colors, 'warning'); $warning: map_get($theme-colors, "warning");
$danger: map_get($theme-colors, 'danger'); $danger: map_get($theme-colors, "danger");
$secondary: map_get($theme-colors, 'secondary'); $secondary: map_get($theme-colors, "secondary");
$secondary-alt: map_get($theme-colors, 'secondary-alt'); $secondary-alt: map_get($theme-colors, "secondary-alt");
@import "~bootstrap/scss/bootstrap.scss"; @import "~bootstrap/scss/bootstrap.scss";

View File

@@ -10,7 +10,7 @@ h1 {
small { small {
color: $text-muted; color: $text-muted;
font-size: $h1-font-size * .5; font-size: $h1-font-size * 0.5;
} }
} }
@@ -28,7 +28,7 @@ h4 {
} }
#duo-frame { #duo-frame {
background: url('../images/loading.svg') 0 0 no-repeat; background: url("../images/loading.svg") 0 0 no-repeat;
height: 380px; height: 380px;
iframe { iframe {
@@ -138,7 +138,6 @@ ul.testing-list {
&:focus, &:focus,
&.focus { &.focus {
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), .5); box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), 0.5);
} }
} }

View File

@@ -1,6 +1,6 @@
$fa-font-path: "~font-awesome/fonts"; $fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome.scss"; @import "~font-awesome/scss/font-awesome.scss";
@import '~ngx-toastr/toastr'; @import "~ngx-toastr/toastr";
@import "~bootstrap/scss/_variables.scss"; @import "~bootstrap/scss/_variables.scss";
@@ -47,7 +47,8 @@ $fa-font-path: "~font-awesome/fonts";
} }
} }
&.toast-danger, &.toast-error { &.toast-danger,
&.toast-error {
background-color: $danger; background-color: $danger;
.icon i::before { .icon i::before {

View File

@@ -1,10 +1,10 @@
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { ApiService as ApiServiceBase } from 'jslib-common/services/api.service'; import { ApiService as ApiServiceBase } from "jslib-common/services/api.service";
export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) { export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) {
try { try {
@@ -19,9 +19,14 @@ export async function refreshToken(apiKeyService: ApiKeyService, authService: Au
} }
export class ApiService extends ApiServiceBase { export class ApiService extends ApiServiceBase {
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, constructor(
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>, tokenService: TokenService,
customUserAgent: string = null) { platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
private refreshTokenCallback: () => Promise<void>,
logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null
) {
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent); super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
} }

View File

@@ -1,41 +1,63 @@
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service'; import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AppIdService } from 'jslib-common/abstractions/appId.service'; import { AppIdService } from "jslib-common/abstractions/appId.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthService as AuthServiceBase } from 'jslib-common/services/auth.service'; import { AuthService as AuthServiceBase } from "jslib-common/services/auth.service";
import { AuthResult } from 'jslib-common/models/domain/authResult'; import { AuthResult } from "jslib-common/models/domain/authResult";
import { DeviceRequest } from 'jslib-common/models/request/deviceRequest'; import { DeviceRequest } from "jslib-common/models/request/deviceRequest";
import { TokenRequest } from 'jslib-common/models/request/tokenRequest'; import { TokenRequest } from "jslib-common/models/request/tokenRequest";
import { IdentityTokenResponse } from 'jslib-common/models/response/identityTokenResponse'; import { IdentityTokenResponse } from "jslib-common/models/response/identityTokenResponse";
export class AuthService extends AuthServiceBase { export class AuthService extends AuthServiceBase {
constructor(
constructor(cryptoService: CryptoService, apiService: ApiService, userService: UserService, cryptoService: CryptoService,
tokenService: TokenService, appIdService: AppIdService, i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService, userService: UserService,
vaultTimeoutService: VaultTimeoutService, logService: LogService, private apiKeyService: ApiKeyService, tokenService: TokenService,
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService, appIdService: AppIdService,
keyConnectorService: KeyConnectorService) { i18nService: I18nService,
super(cryptoService, apiService, userService, tokenService, appIdService, i18nService, platformUtilsService, platformUtilsService: PlatformUtilsService,
messagingService, vaultTimeoutService, logService, cryptoFunctionService, environmentService, messagingService: MessagingService,
keyConnectorService, false); vaultTimeoutService: VaultTimeoutService,
logService: LogService,
private apiKeyService: ApiKeyService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
keyConnectorService: KeyConnectorService
) {
super(
cryptoService,
apiService,
userService,
tokenService,
appIdService,
i18nService,
platformUtilsService,
messagingService,
vaultTimeoutService,
logService,
cryptoFunctionService,
environmentService,
keyConnectorService,
false
);
} }
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> { async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
this.selectedTwoFactorProviderType = null; this.selectedTwoFactorProviderType = null;
if (clientId.startsWith('organization')) { if (clientId.startsWith("organization")) {
return await this.organizationLogInHelper(clientId, clientSecret); return await this.organizationLogInHelper(clientId, clientSecret);
} }
return await super.logInApiKey(clientId, clientSecret); return await super.logInApiKey(clientId, clientSecret);
@@ -49,8 +71,16 @@ export class AuthService extends AuthServiceBase {
private async organizationLogInHelper(clientId: string, clientSecret: string) { private async organizationLogInHelper(clientId: string, clientSecret: string) {
const appId = await this.appIdService.getAppId(); const appId = await this.appIdService.getAppId();
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
const request = new TokenRequest(null, null, [clientId, clientSecret], null, const request = new TokenRequest(
null, false, null, deviceRequest); null,
null,
[clientId, clientSecret],
null,
null,
false,
null,
deviceRequest
);
const response = await this.apiService.postIdentityToken(request); const response = await this.apiService.postIdentityToken(request);
const result = new AuthResult(); const result = new AuthResult();

View File

@@ -1,29 +1,29 @@
import * as graph from '@microsoft/microsoft-graph-client'; import * as graph from "@microsoft/microsoft-graph-client";
import * as graphType from '@microsoft/microsoft-graph-types'; import * as graphType from "@microsoft/microsoft-graph-types";
import * as https from 'https'; import * as https from "https";
import * as querystring from 'querystring'; import * as querystring from "querystring";
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { AzureConfiguration } from '../models/azureConfiguration'; import { AzureConfiguration } from "../models/azureConfiguration";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service'; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } from "./directory.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
const AzurePublicIdentityAuhtority = 'login.microsoftonline.com'; const AzurePublicIdentityAuhtority = "login.microsoftonline.com";
const AzureGovermentIdentityAuhtority = 'login.microsoftonline.us'; const AzureGovermentIdentityAuhtority = "login.microsoftonline.us";
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'; const UserSelectParams = "?$select=id,mail,userPrincipalName,displayName,accountEnabled";
enum UserSetType { enum UserSetType {
IncludeUser, IncludeUser,
@@ -39,8 +39,11 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
private accessToken: string; private accessToken: string;
private accessTokenExpiration: Date; private accessTokenExpiration: Date;
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private i18nService: I18nService) { private configurationService: ConfigurationService,
private logService: LogService,
private i18nService: I18nService
) {
super(); super();
this.init(); this.init();
} }
@@ -52,7 +55,8 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
} }
this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>( this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory); DirectoryType.AzureActiveDirectory
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
@@ -82,7 +86,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
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' + UserSelectParams); 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) {
@@ -97,8 +101,11 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
continue; continue;
} }
if (!entry.disabled && !entry.deleted && if (
(entry.email == null || entry.email.indexOf('#') > -1)) { !entry.disabled &&
!entry.deleted &&
(entry.email == null || entry.email.indexOf("#") > -1)
) {
continue; continue;
} }
@@ -134,7 +141,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
} }
if (res == null) { if (res == null) {
const userReq = this.client.api('/users/delta' + UserSelectParams); const userReq = this.client.api("/users/delta" + UserSelectParams);
res = await userReq.get(); res = await userReq.get();
} }
@@ -174,42 +181,43 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
} }
private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> { private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> {
if (filter == null || filter === '') { if (filter == null || filter === "") {
return null; return null;
} }
const mainParts = filter.split('|'); const mainParts = filter.split("|");
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') { if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
return null; return null;
} }
const parts = mainParts[0].split(':'); const parts = mainParts[0].split(":");
if (parts.length !== 2) { if (parts.length !== 2) {
return null; return null;
} }
const keyword = parts[0].trim().toLowerCase(); const keyword = parts[0].trim().toLowerCase();
let exclude = true; let exclude = true;
if (keyword === 'include') { if (keyword === "include") {
exclude = false; exclude = false;
} else if (keyword === 'exclude') { } else if (keyword === "exclude") {
exclude = true; exclude = true;
} else if (keyword === 'excludeadministrativeunit') { } else if (keyword === "excludeadministrativeunit") {
exclude = true; exclude = true;
} else if (keyword === 'includeadministrativeunit') { } else if (keyword === "includeadministrativeunit") {
exclude = false; exclude = false;
} else { } else {
return null; return null;
} }
const set = new Set<string>(); const set = new Set<string>();
const pieces = parts[1].split(','); const pieces = parts[1].split(",");
if (keyword === 'excludeadministrativeunit' || keyword === 'includeadministrativeunit') { if (keyword === "excludeadministrativeunit" || keyword === "includeadministrativeunit") {
for (const p of pieces) { for (const p of pieces) {
const auMembers = await this.client const auMembers = await this.client
.api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`).get(); .api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`)
.get();
for (const auMember of auMembers.value) { for (const auMember of auMembers.value) {
if (auMember['@odata.type'] === '#microsoft.graph.group') { if (auMember["@odata.type"] === "#microsoft.graph.group") {
set.add(auMember.displayName.toLowerCase()); set.add(auMember.displayName.toLowerCase());
} }
} }
@@ -223,36 +231,36 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
} }
private createCustomUserSet(filter: string): [UserSetType, Set<string>] { private createCustomUserSet(filter: string): [UserSetType, Set<string>] {
if (filter == null || filter === '') { if (filter == null || filter === "") {
return null; return null;
} }
const mainParts = filter.split('|'); const mainParts = filter.split("|");
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') { if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
return null; return null;
} }
const parts = mainParts[0].split(':'); const parts = mainParts[0].split(":");
if (parts.length !== 2) { if (parts.length !== 2) {
return null; return null;
} }
const keyword = parts[0].trim().toLowerCase(); const keyword = parts[0].trim().toLowerCase();
let userSetType = UserSetType.IncludeUser; let userSetType = UserSetType.IncludeUser;
if (keyword === 'include') { if (keyword === "include") {
userSetType = UserSetType.IncludeUser; userSetType = UserSetType.IncludeUser;
} else if (keyword === 'exclude') { } else if (keyword === "exclude") {
userSetType = UserSetType.ExcludeUser; userSetType = UserSetType.ExcludeUser;
} else if (keyword === 'includegroup') { } else if (keyword === "includegroup") {
userSetType = UserSetType.IncludeGroup; userSetType = UserSetType.IncludeGroup;
} else if (keyword === 'excludegroup') { } else if (keyword === "excludegroup") {
userSetType = UserSetType.ExcludeGroup; userSetType = UserSetType.ExcludeGroup;
} else { } else {
return null; return null;
} }
const set = new Set<string>(); const set = new Set<string>();
const pieces = parts[1].split(','); const pieces = parts[1].split(",");
for (const p of pieces) { for (const p of pieces) {
set.add(p.trim().toLowerCase()); set.add(p.trim().toLowerCase());
} }
@@ -260,8 +268,11 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
return [userSetType, set]; return [userSetType, set];
} }
private async filterOutUserResult(setFilter: [UserSetType, Set<string>], user: UserEntry, private async filterOutUserResult(
checkGroupsFilter: boolean): Promise<boolean> { setFilter: [UserSetType, Set<string>],
user: UserEntry,
checkGroupsFilter: boolean
): Promise<boolean> {
if (setFilter == null) { if (setFilter == null) {
return false; return false;
} }
@@ -303,8 +314,10 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
entry.externalId = user.id; entry.externalId = user.id;
entry.email = user.mail; entry.email = user.mail;
if (user.userPrincipalName && (entry.email == null || entry.email === '' || if (
entry.email.indexOf('onmicrosoft.com') > -1)) { user.userPrincipalName &&
(entry.email == null || entry.email === "" || entry.email.indexOf("onmicrosoft.com") > -1)
) {
entry.email = user.userPrincipalName; entry.email = user.userPrincipalName;
} }
@@ -314,7 +327,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled; entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled;
if ((user as any)['@removed'] != null && (user as any)['@removed'].reason === 'changed') { if ((user as any)["@removed"] != null && (user as any)["@removed"].reason === "changed") {
entry.deleted = true; entry.deleted = true;
} }
@@ -324,7 +337,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> { private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
const entryIds = new Set<string>(); const entryIds = new Set<string>();
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const groupsReq = this.client.api('/groups'); const groupsReq = this.client.api("/groups");
let res = await groupsReq.get(); let res = await groupsReq.get();
while (true) { while (true) {
const groups: graphType.Group[] = res.value; const groups: graphType.Group[] = res.value;
@@ -360,15 +373,15 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
entry.externalId = group.id; entry.externalId = group.id;
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");
let memRes = await memReq.get(); let memRes = await memReq.get();
while (true) { while (true) {
const members: any = memRes.value; const members: any = memRes.value;
if (members != null) { if (members != null) {
for (const member of members) { for (const member of members) {
if (member[ObjectType] === '#microsoft.graph.group') { if (member[ObjectType] === "#microsoft.graph.group") {
entry.groupMemberReferenceIds.add((member as graphType.Group).id); entry.groupMemberReferenceIds.add((member as graphType.Group).id);
} else if (member[ObjectType] === '#microsoft.graph.user') { } else if (member[ObjectType] === "#microsoft.graph.user") {
entry.userMemberExternalIds.add((member as graphType.User).id); entry.userMemberExternalIds.add((member as graphType.User).id);
} }
} }
@@ -386,16 +399,25 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
private init() { private init() {
this.client = graph.Client.init({ this.client = graph.Client.init({
authProvider: done => { authProvider: (done) => {
if (this.dirConfig.applicationId == null || this.dirConfig.key == null || if (
this.dirConfig.tenant == null) { this.dirConfig.applicationId == null ||
done(new Error(this.i18nService.t('dirConfigIncomplete')), null); this.dirConfig.key == null ||
this.dirConfig.tenant == null
) {
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
return; return;
} }
const identityAuthority = this.dirConfig.identityAuthority != null ? this.dirConfig.identityAuthority : AzurePublicIdentityAuhtority; const identityAuthority =
if (identityAuthority !== AzurePublicIdentityAuhtority && identityAuthority !== AzureGovermentIdentityAuhtority) { this.dirConfig.identityAuthority != null
done(new Error(this.i18nService.t('dirConfigIncomplete')), null); ? this.dirConfig.identityAuthority
: AzurePublicIdentityAuhtority;
if (
identityAuthority !== AzurePublicIdentityAuhtority &&
identityAuthority !== AzureGovermentIdentityAuhtority
) {
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
return; return;
} }
@@ -410,37 +432,42 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
const data = querystring.stringify({ const data = querystring.stringify({
client_id: this.dirConfig.applicationId, client_id: this.dirConfig.applicationId,
client_secret: this.dirConfig.key, client_secret: this.dirConfig.key,
grant_type: 'client_credentials', grant_type: "client_credentials",
scope: 'https://graph.microsoft.com/.default', scope: "https://graph.microsoft.com/.default",
}); });
const req = https.request({ const req = https
.request(
{
host: identityAuthority, host: identityAuthority,
path: '/' + this.dirConfig.tenant + '/oauth2/v2.0/token', path: "/" + this.dirConfig.tenant + "/oauth2/v2.0/token",
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', "Content-Type": "application/x-www-form-urlencoded",
'Content-Length': Buffer.byteLength(data), "Content-Length": Buffer.byteLength(data),
}, },
}, res => { },
res.setEncoding('utf8'); (res) => {
res.on('data', (chunk: string) => { res.setEncoding("utf8");
res.on("data", (chunk: string) => {
const d = JSON.parse(chunk); const d = JSON.parse(chunk);
if (res.statusCode === 200 && d.access_token != null) { if (res.statusCode === 200 && d.access_token != null) {
this.setAccessTokenExpiration(d.access_token, d.expires_in); this.setAccessTokenExpiration(d.access_token, d.expires_in);
done(null, d.access_token); done(null, d.access_token);
} else if (d.error != null && d.error_description != null) { } else if (d.error != null && d.error_description != null) {
const shortError = d.error_description?.split('\n', 1)[0]; const shortError = d.error_description?.split("\n", 1)[0];
const err = new Error(d.error + ' (' + res.statusCode + '): ' + shortError); const err = new Error(d.error + " (" + res.statusCode + "): " + shortError);
// tslint:disable-next-line // tslint:disable-next-line
console.error(d.error_description); console.error(d.error_description);
done(err, null); done(err, null);
} else { } else {
const err = new Error('Unknown error (' + res.statusCode + ').'); const err = new Error("Unknown error (" + res.statusCode + ").");
done(err, null); done(err, null);
} }
}); });
}).on('error', err => { }
)
.on("error", (err) => {
done(err, null); done(err, null);
}); });

View File

@@ -1,16 +1,16 @@
import { SyncConfiguration } from '../models/syncConfiguration'; 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";
export abstract class BaseDirectoryService { export abstract class BaseDirectoryService {
protected createDirectoryQuery(filter: string) { protected createDirectoryQuery(filter: string) {
if (filter == null || filter === '') { if (filter == null || filter === "") {
return null; return null;
} }
const mainParts = filter.split('|'); const mainParts = filter.split("|");
if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === '') { if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === "") {
return null; return null;
} }
@@ -18,32 +18,32 @@ export abstract class BaseDirectoryService {
} }
protected createCustomSet(filter: string): [boolean, Set<string>] { protected createCustomSet(filter: string): [boolean, Set<string>] {
if (filter == null || filter === '') { if (filter == null || filter === "") {
return null; return null;
} }
const mainParts = filter.split('|'); const mainParts = filter.split("|");
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') { if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
return null; return null;
} }
const parts = mainParts[0].split(':'); const parts = mainParts[0].split(":");
if (parts.length !== 2) { if (parts.length !== 2) {
return null; return null;
} }
const keyword = parts[0].trim().toLowerCase(); const keyword = parts[0].trim().toLowerCase();
let exclude = true; let exclude = true;
if (keyword === 'include') { if (keyword === "include") {
exclude = false; exclude = false;
} else if (keyword === 'exclude') { } else if (keyword === "exclude") {
exclude = true; exclude = true;
} else { } else {
return null; return null;
} }
const set = new Set<string>(); const set = new Set<string>();
const pieces = parts[1].split(','); const pieces = parts[1].split(",");
for (const p of pieces) { for (const p of pieces) {
set.add(p.trim().toLowerCase()); set.add(p.trim().toLowerCase());
} }
@@ -53,7 +53,7 @@ export abstract class BaseDirectoryService {
protected filterOutResult(setFilter: [boolean, Set<string>], result: string) { protected filterOutResult(setFilter: [boolean, Set<string>], result: string) {
if (setFilter != null) { if (setFilter != null) {
const cleanResult = result != null ? result.trim().toLowerCase() : '--'; const cleanResult = result != null ? result.trim().toLowerCase() : "--";
const excluded = setFilter[0]; const excluded = setFilter[0];
const set = setFilter[1]; const set = setFilter[1];
@@ -67,13 +67,17 @@ export abstract class BaseDirectoryService {
return false; return false;
} }
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[], protected filterUsersFromGroupsSet(
setFilter: [boolean, Set<string>], syncConfig: SyncConfiguration): UserEntry[] { users: UserEntry[],
groups: GroupEntry[],
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.deleted) { if (u.deleted) {
return true; return true;
} }
@@ -81,11 +85,11 @@ export abstract class BaseDirectoryService {
return true; return true;
} }
return groups.filter(g => g.userMemberExternalIds.has(u.externalId)).length > 0; return groups.filter((g) => g.userMemberExternalIds.has(u.externalId)).length > 0;
}); });
} }
protected forceGroup(force: boolean, users: UserEntry[]): boolean { protected forceGroup(force: boolean, users: UserEntry[]): boolean {
return force || (users != null && users.filter(u => !u.deleted && !u.disabled).length > 0); return force || (users != null && users.filter((u) => !u.deleted && !u.disabled).length > 0);
} }
} }

View File

@@ -1,34 +1,37 @@
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { AzureConfiguration } from '../models/azureConfiguration'; 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 { OneLoginConfiguration } from "../models/oneLoginConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
const StoredSecurely = '[STORED SECURELY]'; const StoredSecurely = "[STORED SECURELY]";
const Keys = { const Keys = {
ldap: 'ldapPassword', ldap: "ldapPassword",
gsuite: 'gsuitePrivateKey', gsuite: "gsuitePrivateKey",
azure: 'azureKey', azure: "azureKey",
okta: 'oktaToken', okta: "oktaToken",
oneLogin: 'oneLoginClientSecret', oneLogin: "oneLoginClientSecret",
directoryConfigPrefix: 'directoryConfig_', directoryConfigPrefix: "directoryConfig_",
sync: 'syncConfig', sync: "syncConfig",
directoryType: 'directoryType', directoryType: "directoryType",
userDelta: 'userDeltaToken', userDelta: "userDeltaToken",
groupDelta: 'groupDeltaToken', groupDelta: "groupDeltaToken",
lastUserSync: 'lastUserSync', lastUserSync: "lastUserSync",
lastGroupSync: 'lastGroupSync', lastGroupSync: "lastGroupSync",
lastSyncHash: 'lastSyncHash', lastSyncHash: "lastSyncHash",
organizationId: 'organizationId', organizationId: "organizationId",
}; };
export class ConfigurationService { export class ConfigurationService {
constructor(private storageService: StorageService, private secureStorageService: StorageService, constructor(
private useSecureStorageForSecrets = true) { } 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);
@@ -58,9 +61,15 @@ export class ConfigurationService {
return config; return config;
} }
async saveDirectory(type: DirectoryType, async saveDirectory(
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration | type: DirectoryType,
OneLoginConfiguration): Promise<any> { config:
| LdapConfiguration
| GSuiteConfiguration
| AzureConfiguration
| OktaConfiguration
| OneLoginConfiguration
): Promise<any> {
const savedConfig: any = Object.assign({}, config); const savedConfig: any = Object.assign({}, config);
if (this.useSecureStorageForSecrets) { if (this.useSecureStorageForSecrets) {
switch (type) { switch (type) {
@@ -93,7 +102,7 @@ export class ConfigurationService {
await this.secureStorageService.remove(Keys.gsuite); await this.secureStorageService.remove(Keys.gsuite);
} else { } else {
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey = (config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
savedConfig.privateKey.replace(/\\n/g, '\n'); savedConfig.privateKey.replace(/\\n/g, "\n");
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey); await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
savedConfig.privateKey = StoredSecurely; savedConfig.privateKey = StoredSecurely;
} }

View File

@@ -1,5 +1,5 @@
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
export interface IDirectoryService { export interface IDirectoryService {
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>; getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;

View File

@@ -1,22 +1,19 @@
import { JWT } from 'google-auth-library'; import { JWT } from "google-auth-library";
import { import { admin_directory_v1, google } from "googleapis";
admin_directory_v1,
google,
} from 'googleapis';
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { GSuiteConfiguration } from '../models/gsuiteConfiguration'; import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service'; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } from "./directory.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService { export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
private client: JWT; private client: JWT;
@@ -25,10 +22,13 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
private dirConfig: GSuiteConfiguration; private dirConfig: GSuiteConfiguration;
private syncConfig: SyncConfiguration; private syncConfig: SyncConfiguration;
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private i18nService: I18nService) { private configurationService: ConfigurationService,
private logService: LogService,
private i18nService: I18nService
) {
super(); super();
this.service = google.admin('directory_v1'); this.service = google.admin("directory_v1");
} }
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
@@ -37,7 +37,9 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite); this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>(
DirectoryType.GSuite
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
@@ -71,11 +73,11 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
const filter = this.createCustomSet(this.syncConfig.userFilter); const filter = this.createCustomSet(this.syncConfig.userFilter);
while (true) { while (true) {
this.logService.info('Querying users - nextPageToken:' + nextPageToken); this.logService.info("Querying users - nextPageToken:" + nextPageToken);
const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams); const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams);
const res = await this.service.users.list(p); const res = await this.service.users.list(p);
if (res.status !== 200) { if (res.status !== 200) {
throw new Error('User list API failed: ' + res.statusText); throw new Error("User list API failed: " + res.statusText);
} }
nextPageToken = res.data.nextPageToken; nextPageToken = res.data.nextPageToken;
@@ -98,11 +100,14 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
nextPageToken = null; nextPageToken = null;
while (true) { while (true) {
this.logService.info('Querying deleted users - nextPageToken:' + nextPageToken); this.logService.info("Querying deleted users - nextPageToken:" + nextPageToken);
const p = Object.assign({ showDeleted: true, query: query, pageToken: nextPageToken }, this.authParams); const p = Object.assign(
{ showDeleted: true, query: query, pageToken: nextPageToken },
this.authParams
);
const delRes = await this.service.users.list(p); const delRes = await this.service.users.list(p);
if (delRes.status !== 200) { if (delRes.status !== 200) {
throw new Error('Deleted user list API failed: ' + delRes.statusText); throw new Error("Deleted user list API failed: " + delRes.statusText);
} }
nextPageToken = delRes.data.nextPageToken; nextPageToken = delRes.data.nextPageToken;
@@ -127,7 +132,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
} }
private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) { private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) {
if ((user.emails == null || user.emails === '') && !deleted) { if ((user.emails == null || user.emails === "") && !deleted) {
return null; return null;
} }
@@ -140,16 +145,19 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
return entry; return entry;
} }
private async getGroups(setFilter: [boolean, Set<string>], users: UserEntry[]): 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;
while (true) { while (true) {
this.logService.info('Querying groups - nextPageToken:' + nextPageToken); this.logService.info("Querying groups - nextPageToken:" + nextPageToken);
const p = Object.assign({ pageToken: nextPageToken }, this.authParams); const p = Object.assign({ pageToken: nextPageToken }, this.authParams);
const res = await this.service.groups.list(p); const res = await this.service.groups.list(p);
if (res.status !== 200) { if (res.status !== 200) {
throw new Error('Group list API failed: ' + res.statusText); throw new Error("Group list API failed: " + res.statusText);
} }
nextPageToken = res.data.nextPageToken; nextPageToken = res.data.nextPageToken;
@@ -182,7 +190,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams); const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams);
const memRes = await this.service.members.list(p); const memRes = await this.service.members.list(p);
if (memRes.status !== 200) { if (memRes.status !== 200) {
this.logService.warning('Group member list API failed: ' + memRes.statusText); this.logService.warning("Group member list API failed: " + memRes.statusText);
return entry; return entry;
} }
@@ -193,14 +201,14 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
continue; 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') { if (member.status == null || member.status.toLowerCase() !== "active") {
continue; 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') { } else if (type === "customer") {
for (const user of users) { for (const user of users) {
entry.userMemberExternalIds.add(user.externalId); entry.userMemberExternalIds.add(user.externalId);
} }
@@ -217,9 +225,13 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
} }
private async auth() { private async auth() {
if (this.dirConfig.clientEmail == null || this.dirConfig.privateKey == null || if (
this.dirConfig.adminUser == null || this.dirConfig.domain == null) { this.dirConfig.clientEmail == null ||
throw new Error(this.i18nService.t('dirConfigIncomplete')); this.dirConfig.privateKey == null ||
this.dirConfig.adminUser == null ||
this.dirConfig.domain == null
) {
throw new Error(this.i18nService.t("dirConfigIncomplete"));
} }
this.client = new google.auth.JWT({ this.client = new google.auth.JWT({
@@ -227,9 +239,9 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
key: this.dirConfig.privateKey != null ? this.dirConfig.privateKey.trimLeft() : null, key: this.dirConfig.privateKey != null ? this.dirConfig.privateKey.trimLeft() : null,
subject: this.dirConfig.adminUser, subject: this.dirConfig.adminUser,
scopes: [ scopes: [
'https://www.googleapis.com/auth/admin.directory.user.readonly', "https://www.googleapis.com/auth/admin.directory.user.readonly",
'https://www.googleapis.com/auth/admin.directory.group.readonly', "https://www.googleapis.com/auth/admin.directory.group.readonly",
'https://www.googleapis.com/auth/admin.directory.group.member.readonly', "https://www.googleapis.com/auth/admin.directory.group.member.readonly",
], ],
}); });
@@ -238,10 +250,10 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
this.authParams = { this.authParams = {
auth: this.client, auth: this.client,
}; };
if (this.dirConfig.domain != null && this.dirConfig.domain.trim() !== '') { if (this.dirConfig.domain != null && this.dirConfig.domain.trim() !== "") {
this.authParams.domain = this.dirConfig.domain; this.authParams.domain = this.dirConfig.domain;
} }
if (this.dirConfig.customer != null && this.dirConfig.customer.trim() !== '') { if (this.dirConfig.customer != null && this.dirConfig.customer.trim() !== "") {
this.authParams.customer = this.dirConfig.customer; this.authParams.customer = this.dirConfig.customer;
} }
} }

View File

@@ -1,14 +1,17 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { I18nService as BaseI18nService } from 'jslib-common/services/i18n.service'; import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service";
export class I18nService extends BaseI18nService { export class I18nService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) { constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => { super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json'); const filePath = path.join(
const localesJson = fs.readFileSync(filePath, 'utf8'); __dirname,
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM this.localesDirectory + "/" + formattedLocale + "/messages.json"
);
const localesJson = fs.readFileSync(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales); return Promise.resolve(locales);
}); });
} }

View File

@@ -1,16 +1,12 @@
import { import { deletePassword, getPassword, setPassword } from "keytar";
deletePassword,
getPassword,
setPassword,
} from 'keytar';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
export class KeytarSecureStorageService implements StorageService { export class KeytarSecureStorageService implements StorageService {
constructor(private serviceName: string) {} constructor(private serviceName: string) {}
get<T>(key: string): Promise<T> { get<T>(key: string): Promise<T> {
return getPassword(this.serviceName, key).then(val => { return getPassword(this.serviceName, key).then((val) => {
return JSON.parse(val) as T; return JSON.parse(val) as T;
}); });
} }

View File

@@ -1,22 +1,22 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as ldap from 'ldapjs'; import * as ldap from "ldapjs";
import { checkServerIdentity, PeerCertificate } from 'tls'; import { checkServerIdentity, PeerCertificate } from "tls";
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { LdapConfiguration } from '../models/ldapConfiguration'; import { LdapConfiguration } from "../models/ldapConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } from "./directory.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
const UserControlAccountDisabled = 2; const UserControlAccountDisabled = 2;
@@ -25,8 +25,11 @@ export class LdapDirectoryService implements IDirectoryService {
private dirConfig: LdapConfiguration; private dirConfig: LdapConfiguration;
private syncConfig: SyncConfiguration; private syncConfig: SyncConfiguration;
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private i18nService: I18nService) { } private configurationService: ConfigurationService,
private logService: LogService,
private i18nService: I18nService
) {}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType(); const type = await this.configurationService.getDirectoryType();
@@ -34,7 +37,9 @@ export class LdapDirectoryService implements IDirectoryService {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap); this.dirConfig = await this.configurationService.getDirectory<LdapConfiguration>(
DirectoryType.Ldap
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
@@ -55,7 +60,7 @@ export class LdapDirectoryService implements IDirectoryService {
if (this.syncConfig.groups) { if (this.syncConfig.groups) {
let groupForce = force; let groupForce = force;
if (!groupForce && users != null) { if (!groupForce && users != null) {
const activeUsers = users.filter(u => !u.deleted && !u.disabled); const activeUsers = users.filter((u) => !u.deleted && !u.disabled);
groupForce = activeUsers.length > 0; groupForce = activeUsers.length > 0;
} }
groups = await this.getGroups(groupForce); groups = await this.getGroups(groupForce);
@@ -71,26 +76,35 @@ export class LdapDirectoryService implements IDirectoryService {
filter = this.buildRevisionFilter(filter, force, lastSync); filter = this.buildRevisionFilter(filter, force, lastSync);
const path = this.makeSearchPath(this.syncConfig.userPath); const path = this.makeSearchPath(this.syncConfig.userPath);
this.logService.info('User search: ' + path + ' => ' + filter); this.logService.info("User search: " + path + " => " + filter);
const regularUsers = await this.search<UserEntry>(path, filter, (se: any) => this.buildUser(se, false)); const regularUsers = await this.search<UserEntry>(path, filter, (se: any) =>
this.buildUser(se, false)
);
if (!this.dirConfig.ad) { if (!this.dirConfig.ad) {
return regularUsers; return regularUsers;
} }
try { try {
let deletedFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, '(isDeleted=TRUE)'); let deletedFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, "(isDeleted=TRUE)");
deletedFilter = this.buildRevisionFilter(deletedFilter, force, lastSync); deletedFilter = this.buildRevisionFilter(deletedFilter, force, lastSync);
const deletedPath = this.makeSearchPath('CN=Deleted Objects'); const deletedPath = this.makeSearchPath("CN=Deleted Objects");
this.logService.info('Deleted user search: ' + deletedPath + ' => ' + deletedFilter); this.logService.info("Deleted user search: " + deletedPath + " => " + deletedFilter);
const delControl = new (ldap as any).Control({ type: '1.2.840.113556.1.4.417', criticality: true }); const delControl = new (ldap as any).Control({
const deletedUsers = await this.search<UserEntry>(deletedPath, deletedFilter, type: "1.2.840.113556.1.4.417",
(se: any) => this.buildUser(se, true), [delControl]); criticality: true,
});
const deletedUsers = await this.search<UserEntry>(
deletedPath,
deletedFilter,
(se: any) => this.buildUser(se, true),
[delControl]
);
return regularUsers.concat(deletedUsers); return regularUsers.concat(deletedUsers);
} catch (e) { } catch (e) {
this.logService.warning('Cannot query deleted users.'); this.logService.warning("Cannot query deleted users.");
return regularUsers; return regularUsers;
} }
} }
@@ -107,8 +121,12 @@ export class LdapDirectoryService implements IDirectoryService {
user.externalId = this.getExternalId(searchEntry, user.referenceId); user.externalId = this.getExternalId(searchEntry, user.referenceId);
user.disabled = this.entryDisabled(searchEntry); user.disabled = this.entryDisabled(searchEntry);
user.email = this.getAttr(searchEntry, this.syncConfig.userEmailAttribute); user.email = this.getAttr(searchEntry, this.syncConfig.userEmailAttribute);
if (user.email == null && this.syncConfig.useEmailPrefixSuffix && if (
this.syncConfig.emailPrefixAttribute != null && this.syncConfig.emailSuffix != null) { user.email == null &&
this.syncConfig.useEmailPrefixSuffix &&
this.syncConfig.emailPrefixAttribute != null &&
this.syncConfig.emailSuffix != null
) {
const prefixAttr = this.getAttr(searchEntry, this.syncConfig.emailPrefixAttribute); const prefixAttr = this.getAttr(searchEntry, this.syncConfig.emailPrefixAttribute);
if (prefixAttr != null) { if (prefixAttr != null) {
user.email = prefixAttr + this.syncConfig.emailSuffix; user.email = prefixAttr + this.syncConfig.emailSuffix;
@@ -119,7 +137,7 @@ export class LdapDirectoryService implements IDirectoryService {
user.email = user.email.trim().toLowerCase(); user.email = user.email.trim().toLowerCase();
} }
if (!user.deleted && (user.email == null || user.email.trim() === '')) { if (!user.deleted && (user.email == null || user.email.trim() === "")) {
return null; return null;
} }
@@ -130,14 +148,17 @@ export class LdapDirectoryService implements IDirectoryService {
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const lastSync = await this.configurationService.getLastUserSyncDate(); const lastSync = await this.configurationService.getLastUserSyncDate();
const originalFilter = this.buildBaseFilter(this.syncConfig.groupObjectClass, this.syncConfig.groupFilter); const originalFilter = this.buildBaseFilter(
this.syncConfig.groupObjectClass,
this.syncConfig.groupFilter
);
let filter = originalFilter; let filter = originalFilter;
const revisionFilter = this.buildRevisionFilter(filter, force, lastSync); const revisionFilter = this.buildRevisionFilter(filter, force, lastSync);
const searchSinceRevision = filter !== revisionFilter; const searchSinceRevision = filter !== revisionFilter;
filter = revisionFilter; filter = revisionFilter;
const path = this.makeSearchPath(this.syncConfig.groupPath); const path = this.makeSearchPath(this.syncConfig.groupPath);
this.logService.info('Group search: ' + path + ' => ' + filter); this.logService.info("Group search: " + path + " => " + filter);
let groupSearchEntries: any[] = []; let groupSearchEntries: any[] = [];
const initialSearchGroupIds = await this.search<string>(path, filter, (se: any) => { const initialSearchGroupIds = await this.search<string>(path, filter, (se: any) => {
@@ -151,7 +172,10 @@ export class LdapDirectoryService implements IDirectoryService {
groupSearchEntries = await this.search<string>(path, originalFilter, (se: any) => se); groupSearchEntries = await this.search<string>(path, originalFilter, (se: any) => se);
} }
const userFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter); const userFilter = this.buildBaseFilter(
this.syncConfig.userObjectClass,
this.syncConfig.userFilter
);
const userPath = this.makeSearchPath(this.syncConfig.userPath); const userPath = this.makeSearchPath(this.syncConfig.userPath);
const userIdMap = new Map<string, string>(); const userIdMap = new Map<string, string>();
await this.search<string>(userPath, userFilter, (se: any) => { await this.search<string>(userPath, userFilter, (se: any) => {
@@ -180,7 +204,7 @@ export class LdapDirectoryService implements IDirectoryService {
group.name = this.getAttr(searchEntry, this.syncConfig.groupNameAttribute); group.name = this.getAttr(searchEntry, this.syncConfig.groupNameAttribute);
if (group.name == null) { if (group.name == null) {
group.name = this.getAttr(searchEntry, 'cn'); group.name = this.getAttr(searchEntry, "cn");
} }
if (group.name == null) { if (group.name == null) {
@@ -202,7 +226,7 @@ export class LdapDirectoryService implements IDirectoryService {
} }
private getExternalId(searchEntry: any, referenceId: string) { private getExternalId(searchEntry: any, referenceId: string) {
const attrObj = this.getAttrObj(searchEntry, 'objectGUID'); const attrObj = this.getAttrObj(searchEntry, "objectGUID");
if (attrObj != null && attrObj._vals != null && attrObj._vals.length > 0) { if (attrObj != null && attrObj._vals != null && attrObj._vals.length > 0) {
return this.bufToGuid(attrObj._vals[0]); return this.bufToGuid(attrObj._vals[0]);
} else { } else {
@@ -212,35 +236,35 @@ export class LdapDirectoryService implements IDirectoryService {
private buildBaseFilter(objectClass: string, subFilter: string): string { private buildBaseFilter(objectClass: string, subFilter: string): string {
let filter = this.buildObjectClassFilter(objectClass); let filter = this.buildObjectClassFilter(objectClass);
if (subFilter != null && subFilter.trim() !== '') { if (subFilter != null && subFilter.trim() !== "") {
filter = '(&' + filter + subFilter + ')'; filter = "(&" + filter + subFilter + ")";
} }
return filter; return filter;
} }
private buildObjectClassFilter(objectClass: string): string { private buildObjectClassFilter(objectClass: string): string {
return '(&(objectClass=' + objectClass + '))'; return "(&(objectClass=" + objectClass + "))";
} }
private buildRevisionFilter(baseFilter: string, force: boolean, lastRevisionDate: Date) { private buildRevisionFilter(baseFilter: string, force: boolean, lastRevisionDate: Date) {
const revisionAttr = this.syncConfig.revisionDateAttribute; const revisionAttr = this.syncConfig.revisionDateAttribute;
if (!force && lastRevisionDate != null && revisionAttr != null && revisionAttr.trim() !== '') { if (!force && lastRevisionDate != null && revisionAttr != null && revisionAttr.trim() !== "") {
const dateString = lastRevisionDate.toISOString().replace(/[-:T]/g, '').substr(0, 16) + 'Z'; const dateString = lastRevisionDate.toISOString().replace(/[-:T]/g, "").substr(0, 16) + "Z";
baseFilter = '(&' + baseFilter + '(' + revisionAttr + '>=' + dateString + '))'; baseFilter = "(&" + baseFilter + "(" + revisionAttr + ">=" + dateString + "))";
} }
return baseFilter; return baseFilter;
} }
private makeSearchPath(pathPrefix: string) { private makeSearchPath(pathPrefix: string) {
if (this.dirConfig.rootPath.toLowerCase().indexOf('dc=') === -1) { if (this.dirConfig.rootPath.toLowerCase().indexOf("dc=") === -1) {
return pathPrefix; 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="));
if (pathPrefix != null && pathPrefix.trim() !== '') { if (pathPrefix != null && pathPrefix.trim() !== "") {
path = pathPrefix.trim() + ',' + path; path = pathPrefix.trim() + "," + path;
} }
return path; return path;
} }
@@ -254,7 +278,12 @@ export class LdapDirectoryService implements IDirectoryService {
} }
const attrs = searchEntry.attributes.filter((a: any) => a.type === attr); const attrs = searchEntry.attributes.filter((a: any) => a.type === attr);
if (attrs == null || attrs.length === 0 || attrs[0].vals == null || attrs[0].vals.length === 0) { if (
attrs == null ||
attrs.length === 0 ||
attrs[0].vals == null ||
attrs[0].vals.length === 0
) {
return null; return null;
} }
@@ -278,7 +307,7 @@ export class LdapDirectoryService implements IDirectoryService {
} }
private entryDisabled(searchEntry: any): boolean { private entryDisabled(searchEntry: any): boolean {
const c = this.getAttr(searchEntry, 'userAccountControl'); const c = this.getAttr(searchEntry, "userAccountControl");
if (c != null) { if (c != null) {
try { try {
const control = parseInt(c, null); const control = parseInt(c, null);
@@ -292,11 +321,15 @@ export class LdapDirectoryService implements IDirectoryService {
return false; return false;
} }
private async search<T>(path: string, filter: string, processEntry: (searchEntry: any) => T, private async search<T>(
controls: ldap.Control[] = []): Promise<T[]> { path: string,
filter: string,
processEntry: (searchEntry: any) => T,
controls: ldap.Control[] = []
): Promise<T[]> {
const options: ldap.SearchOptions = { const options: ldap.SearchOptions = {
filter: filter, filter: filter,
scope: 'sub', scope: "sub",
paged: this.dirConfig.pagedSearch, paged: this.dirConfig.pagedSearch,
}; };
const entries: T[] = []; const entries: T[] = [];
@@ -307,18 +340,18 @@ export class LdapDirectoryService implements IDirectoryService {
return; return;
} }
res.on('error', resErr => { res.on("error", (resErr) => {
reject(resErr); reject(resErr);
}); });
res.on('searchEntry', entry => { res.on("searchEntry", (entry) => {
const e = processEntry(entry); const e = processEntry(entry);
if (e != null) { if (e != null) {
entries.push(e); entries.push(e);
} }
}); });
res.on('end', result => { res.on("end", (result) => {
resolve(entries); resolve(entries);
}); });
}); });
@@ -328,12 +361,11 @@ export class LdapDirectoryService implements IDirectoryService {
private async bind(): Promise<any> { private async bind(): Promise<any> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (this.dirConfig.hostname == null || this.dirConfig.port == null) { if (this.dirConfig.hostname == null || this.dirConfig.port == null) {
reject(this.i18nService.t('dirConfigIncomplete')); reject(this.i18nService.t("dirConfigIncomplete"));
return; return;
} }
const protocol = 'ldap' + (this.dirConfig.ssl && !this.dirConfig.startTls ? 's' : ''); const protocol = "ldap" + (this.dirConfig.ssl && !this.dirConfig.startTls ? "s" : "");
const url = protocol + '://' + 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(),
}; };
@@ -344,21 +376,33 @@ export class LdapDirectoryService implements IDirectoryService {
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized; tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
} }
if (!this.dirConfig.startTls) { if (!this.dirConfig.startTls) {
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' && if (
fs.existsSync(this.dirConfig.sslCaPath)) { this.dirConfig.sslCaPath != null &&
this.dirConfig.sslCaPath !== "" &&
fs.existsSync(this.dirConfig.sslCaPath)
) {
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)]; tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
} }
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' && if (
fs.existsSync(this.dirConfig.sslCertPath)) { this.dirConfig.sslCertPath != null &&
this.dirConfig.sslCertPath !== "" &&
fs.existsSync(this.dirConfig.sslCertPath)
) {
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath); tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
} }
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' && if (
fs.existsSync(this.dirConfig.sslKeyPath)) { this.dirConfig.sslKeyPath != null &&
this.dirConfig.sslKeyPath !== "" &&
fs.existsSync(this.dirConfig.sslKeyPath)
) {
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath); tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
} }
} else { } else {
if (this.dirConfig.tlsCaPath != null && this.dirConfig.tlsCaPath !== '' && if (
fs.existsSync(this.dirConfig.tlsCaPath)) { this.dirConfig.tlsCaPath != null &&
this.dirConfig.tlsCaPath !== "" &&
fs.existsSync(this.dirConfig.tlsCaPath)
) {
tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)]; tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)];
} }
} }
@@ -369,13 +413,17 @@ export class LdapDirectoryService implements IDirectoryService {
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; this.dirConfig.username == null || this.dirConfig.username.trim() === ""
const pass = this.dirConfig.password == null || this.dirConfig.password.trim() === '' ? null : ? null
this.dirConfig.password; : this.dirConfig.username;
const pass =
this.dirConfig.password == null || this.dirConfig.password.trim() === ""
? null
: this.dirConfig.password;
if (user == null || pass == null) { if (user == null || pass == null) {
reject(this.i18nService.t('usernamePasswordNotConfigured')); reject(this.i18nService.t("usernamePasswordNotConfigured"));
return; return;
} }
@@ -384,7 +432,7 @@ export class LdapDirectoryService implements IDirectoryService {
if (err != null) { if (err != null) {
reject(err.message); reject(err.message);
} else { } else {
this.client.bind(user, pass, err2 => { this.client.bind(user, pass, (err2) => {
if (err2 != null) { if (err2 != null) {
reject(err2.message); reject(err2.message);
} else { } else {
@@ -394,7 +442,7 @@ export class LdapDirectoryService implements IDirectoryService {
} }
}); });
} else { } else {
this.client.bind(user, pass, err => { this.client.bind(user, pass, (err) => {
if (err != null) { if (err != null) {
reject(err.message); reject(err.message);
} else { } else {
@@ -407,7 +455,7 @@ export class LdapDirectoryService implements IDirectoryService {
private async unbind(): Promise<void> { private async unbind(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.client.unbind(err => { this.client.unbind((err) => {
if (err != null) { if (err != null) {
reject(err); reject(err);
} else { } else {
@@ -424,8 +472,16 @@ export class LdapDirectoryService implements IDirectoryService {
const p3 = arr.slice(6, 8).reverse().buffer; const p3 = arr.slice(6, 8).reverse().buffer;
const p4 = arr.slice(8, 10).buffer; const p4 = arr.slice(8, 10).buffer;
const p5 = arr.slice(10).buffer; const p5 = arr.slice(10).buffer;
const guid = Utils.fromBufferToHex(p1) + '-' + Utils.fromBufferToHex(p2) + '-' + Utils.fromBufferToHex(p3) + const guid =
'-' + Utils.fromBufferToHex(p4) + '-' + Utils.fromBufferToHex(p5); Utils.fromBufferToHex(p1) +
"-" +
Utils.fromBufferToHex(p2) +
"-" +
Utils.fromBufferToHex(p3) +
"-" +
Utils.fromBufferToHex(p4) +
"-" +
Utils.fromBufferToHex(p5);
return guid.toLowerCase(); return guid.toLowerCase();
} }

View File

@@ -1,20 +1,26 @@
import * as lock from 'proper-lockfile'; import * as lock from "proper-lockfile";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { LowdbStorageService as LowdbStorageServiceBase } from 'jslib-node/services/lowdbStorage.service'; import { LowdbStorageService as LowdbStorageServiceBase } from "jslib-node/services/lowdbStorage.service";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class LowdbStorageService extends LowdbStorageServiceBase { export class LowdbStorageService extends LowdbStorageServiceBase {
constructor(logService: LogService, defaults?: any, dir?: string, allowCache = false, private requireLock = false) { constructor(
logService: LogService,
defaults?: any,
dir?: string,
allowCache = false,
private requireLock = false
) {
super(logService, defaults, dir, allowCache); super(logService, defaults, dir, allowCache);
} }
protected async lockDbFile<T>(action: () => T): Promise<T> { protected async lockDbFile<T>(action: () => T): Promise<T> {
if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) {
this.logService.info('acquiring db file lock'); this.logService.info("acquiring db file lock");
return await lock.lock(this.dataFilePath, { retries: 3 }).then(release => { return await lock.lock(this.dataFilePath, { retries: 3 }).then((release) => {
try { try {
return action(); return action();
} finally { } finally {

View File

@@ -1,14 +1,27 @@
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { NodeApiService as NodeApiServiceBase } from 'jslib-node/services/nodeApi.service'; import { NodeApiService as NodeApiServiceBase } from "jslib-node/services/nodeApi.service";
export class NodeApiService extends NodeApiServiceBase { export class NodeApiService extends NodeApiServiceBase {
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, constructor(
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>, tokenService: TokenService,
customUserAgent: string = null, apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>) { platformUtilsService: PlatformUtilsService,
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent, apiKeyRefresh); environmentService: EnvironmentService,
private refreshTokenCallback: () => Promise<void>,
logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null,
apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>
) {
super(
tokenService,
platformUtilsService,
environmentService,
logoutCallback,
customUserAgent,
apiKeyRefresh
);
} }
doRefreshToken(): Promise<void> { doRefreshToken(): Promise<void> {

View File

@@ -1,18 +1,18 @@
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { OktaConfiguration } from '../models/oktaConfiguration'; import { OktaConfiguration } from "../models/oktaConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service'; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } from "./directory.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import * as https from 'https'; import * as https from "https";
const DelayBetweenBuildGroupCallsInMilliseconds = 500; const DelayBetweenBuildGroupCallsInMilliseconds = 500;
@@ -21,8 +21,11 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
private syncConfig: SyncConfiguration; private syncConfig: SyncConfiguration;
private lastBuildGroupCall: number; private lastBuildGroupCall: number;
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private i18nService: I18nService) { private configurationService: ConfigurationService,
private logService: LogService,
private i18nService: I18nService
) {
super(); super();
} }
@@ -32,7 +35,9 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<OktaConfiguration>(DirectoryType.Okta); this.dirConfig = await this.configurationService.getDirectory<OktaConfiguration>(
DirectoryType.Okta
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
@@ -43,7 +48,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
} }
if (this.dirConfig.orgUrl == null || this.dirConfig.token == null) { if (this.dirConfig.orgUrl == null || this.dirConfig.token == null) {
throw new Error(this.i18nService.t('dirConfigIncomplete')); throw new Error(this.i18nService.t("dirConfigIncomplete"));
} }
let users: UserEntry[]; let users: UserEntry[];
@@ -67,9 +72,10 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync); const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync);
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.apiGetMany('users?filter=' + this.encodeUrlParameter(oktaFilter)) const usersPromise = this.apiGetMany(
.then((users: any[]) => { "users?filter=" + this.encodeUrlParameter(oktaFilter)
).then((users: any[]) => {
for (const user of users) { for (const user of users) {
const entry = this.buildUser(user); const entry = this.buildUser(user);
if (entry != null && !this.filterOutResult(setFilter, entry.email)) { if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
@@ -80,13 +86,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
// 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;
if (oktaFilter == null || oktaFilter.indexOf('lastUpdated ') === -1) { if (oktaFilter == null || oktaFilter.indexOf("lastUpdated ") === -1) {
let deactOktaFilter = 'status eq "DEPROVISIONED"'; let deactOktaFilter = 'status eq "DEPROVISIONED"';
if (oktaFilter != null) { if (oktaFilter != null) {
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter; deactOktaFilter = "(" + oktaFilter + ") and " + deactOktaFilter;
} }
deactUsersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(deactOktaFilter)) deactUsersPromise = this.apiGetMany(
.then((users: any[]) => { "users?filter=" + this.encodeUrlParameter(deactOktaFilter)
).then((users: any[]) => {
for (const user of users) { for (const user of users) {
const entry = this.buildUser(user); const entry = this.buildUser(user);
if (entry != null && !this.filterOutResult(setFilter, entry.email)) { if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
@@ -107,25 +114,32 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
entry.externalId = user.id; entry.externalId = user.id;
entry.referenceId = user.id; entry.referenceId = user.id;
entry.email = user.profile.email != null ? user.profile.email.trim().toLowerCase() : null; entry.email = user.profile.email != null ? user.profile.email.trim().toLowerCase() : null;
entry.deleted = user.status === 'DEPROVISIONED'; entry.deleted = user.status === "DEPROVISIONED";
entry.disabled = user.status === 'SUSPENDED'; entry.disabled = user.status === "SUSPENDED";
return entry; return entry;
} }
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> { private async getGroups(
force: boolean,
setFilter: [boolean, Set<string>]
): Promise<GroupEntry[]> {
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const lastSync = await this.configurationService.getLastGroupSyncDate(); const lastSync = await this.configurationService.getLastGroupSyncDate();
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.apiGetMany('groups?filter=' + this.encodeUrlParameter(oktaFilter)).then(async (groups: any[]) => { await this.apiGetMany("groups?filter=" + this.encodeUrlParameter(oktaFilter)).then(
for (const group of groups.filter(g => !this.filterOutResult(setFilter, g.profile.name))) { async (groups: any[]) => {
for (const group of groups.filter(
(g) => !this.filterOutResult(setFilter, g.profile.name)
)) {
const entry = await this.buildGroup(group); const entry = await this.buildGroup(group);
if (entry != null) { if (entry != null) {
entries.push(entry); entries.push(entry);
} }
} }
}); }
);
return entries; return entries;
} }
@@ -136,14 +150,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
entry.name = group.profile.name; entry.name = group.profile.name;
// throttle some to avoid rate limiting // throttle some to avoid rate limiting
const neededDelay = DelayBetweenBuildGroupCallsInMilliseconds - (Date.now() - this.lastBuildGroupCall); const neededDelay =
DelayBetweenBuildGroupCallsInMilliseconds - (Date.now() - this.lastBuildGroupCall);
if (neededDelay > 0) { if (neededDelay > 0) {
await new Promise(resolve => await new Promise((resolve) => setTimeout(resolve, neededDelay));
setTimeout(resolve, neededDelay));
} }
this.lastBuildGroupCall = Date.now(); this.lastBuildGroupCall = Date.now();
await this.apiGetMany('groups/' + group.id + '/users').then((users: any[]) => { await this.apiGetMany("groups/" + group.id + "/users").then((users: any[]) => {
for (const user of users) { for (const user of users) {
entry.userMemberExternalIds.add(user.id); entry.userMemberExternalIds.add(user.id);
} }
@@ -154,7 +168,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
private buildOktaFilter(baseFilter: string, force: boolean, lastSync: Date) { private buildOktaFilter(baseFilter: string, force: boolean, lastSync: Date) {
baseFilter = this.createDirectoryQuery(baseFilter); baseFilter = this.createDirectoryQuery(baseFilter);
baseFilter = baseFilter == null || baseFilter.trim() === '' ? null : baseFilter; baseFilter = baseFilter == null || baseFilter.trim() === "" ? null : baseFilter;
if (force || lastSync == null) { if (force || lastSync == null) {
return baseFilter; return baseFilter;
} }
@@ -164,32 +178,34 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
return updatedFilter; return updatedFilter;
} }
return '(' + baseFilter + ') and ' + updatedFilter; return "(" + baseFilter + ") and " + updatedFilter;
} }
private encodeUrlParameter(filter: string): string { private encodeUrlParameter(filter: string): string {
return filter == null ? '' : encodeURIComponent(filter); return filter == null ? "" : encodeURIComponent(filter);
} }
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> { private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
const u = new URL(url); const u = new URL(url);
return new Promise(resolve => { return new Promise((resolve) => {
https.get({ https.get(
{
hostname: u.hostname, hostname: u.hostname,
path: u.pathname + u.search, path: u.pathname + u.search,
port: 443, port: 443,
headers: { headers: {
Authorization: 'SSWS ' + this.dirConfig.token, Authorization: "SSWS " + this.dirConfig.token,
Accept: 'application/json', Accept: "application/json",
}, },
}, res => { },
let body = ''; (res) => {
let body = "";
res.on('data', chunk => { res.on("data", (chunk) => {
body += chunk; body += chunk;
}); });
res.on('end', () => { res.on("end", () => {
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
resolve(null); resolve(null);
return; return;
@@ -210,18 +226,20 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
resolve([responseJson, null]); resolve([responseJson, null]);
}); });
res.on('error', () => { res.on("error", () => {
resolve(null); resolve(null);
}); });
}); }
);
}); });
} }
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> { private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
const url = endpoint.indexOf('https://') === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`; const url =
endpoint.indexOf("https://") === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`;
const response = await this.apiGetCall(url); const response = await this.apiGetCall(url);
if (response == null || response[0] == null || !Array.isArray(response[0])) { if (response == null || response[0] == null || !Array.isArray(response[0])) {
throw new Error('API call failed.'); throw new Error("API call failed.");
} }
if (response[0].length === 0) { if (response[0].length === 0) {
return currentData; return currentData;
@@ -230,17 +248,17 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
if (response[1] == null) { if (response[1] == null) {
return currentData; return currentData;
} }
const linkHeader = response[1].get('link'); const linkHeader = response[1].get("link");
if (linkHeader == null || Array.isArray(linkHeader)) { if (linkHeader == null || Array.isArray(linkHeader)) {
return currentData; return currentData;
} }
let nextLink: string = null; let nextLink: string = null;
const linkHeaderParts = linkHeader.split(','); const linkHeaderParts = linkHeader.split(",");
for (const part of linkHeaderParts) { for (const part of linkHeaderParts) {
if (part.indexOf('; rel="next"') > -1) { if (part.indexOf('; rel="next"') > -1) {
const subParts = part.split(';'); const subParts = part.split(";");
if (subParts.length > 0 && subParts[0].indexOf('https://') > -1) { if (subParts.length > 0 && subParts[0].indexOf("https://") > -1) {
nextLink = subParts[0].replace('>', '').replace('<', '').trim(); nextLink = subParts[0].replace(">", "").replace("<", "").trim();
break; break;
} }
} }

View File

@@ -1,16 +1,16 @@
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { OneLoginConfiguration } from '../models/oneLoginConfiguration'; import { OneLoginConfiguration } from "../models/oneLoginConfiguration";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from './baseDirectory.service'; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } from "./directory.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
// Basic email validation: something@something.something // Basic email validation: something@something.something
const ValidEmailRegex = /^\S+@\S+\.\S+$/; const ValidEmailRegex = /^\S+@\S+\.\S+$/;
@@ -21,8 +21,11 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
private accessToken: string; private accessToken: string;
private allUsers: any[] = []; private allUsers: any[] = [];
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private i18nService: I18nService) { private configurationService: ConfigurationService,
private logService: LogService,
private i18nService: I18nService
) {
super(); super();
} }
@@ -32,7 +35,9 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin); this.dirConfig = await this.configurationService.getDirectory<OneLoginConfiguration>(
DirectoryType.OneLogin
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
@@ -43,12 +48,12 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
} }
if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) { if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) {
throw new Error(this.i18nService.t('dirConfigIncomplete')); throw new Error(this.i18nService.t("dirConfigIncomplete"));
} }
this.accessToken = await this.getAccessToken(); this.accessToken = await this.getAccessToken();
if (this.accessToken == null) { if (this.accessToken == null) {
throw new Error('Could not get access token'); throw new Error("Could not get access token");
} }
let users: UserEntry[]; let users: UserEntry[];
@@ -70,9 +75,9 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
const entries: UserEntry[] = []; const entries: UserEntry[] = [];
const query = this.createDirectoryQuery(this.syncConfig.userFilter); const query = this.createDirectoryQuery(this.syncConfig.userFilter);
const setFilter = this.createCustomSet(this.syncConfig.userFilter); const setFilter = this.createCustomSet(this.syncConfig.userFilter);
this.logService.info('Querying users.'); this.logService.info("Querying users.");
this.allUsers = await this.apiGetMany('users' + (query != null ? '?' + query : '')); this.allUsers = await this.apiGetMany("users" + (query != null ? "?" + query : ""));
this.allUsers.forEach(user => { this.allUsers.forEach((user) => {
const entry = this.buildUser(user); const entry = this.buildUser(user);
if (entry != null && !this.filterOutResult(setFilter, entry.email)) { if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
entries.push(entry); entries.push(entry);
@@ -88,7 +93,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
entry.deleted = false; entry.deleted = false;
entry.disabled = user.status === 2; entry.disabled = user.status === 2;
entry.email = user.email; entry.email = user.email;
if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== '') { if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== "") {
if (this.validEmailAddress(user.username)) { if (this.validEmailAddress(user.username)) {
entry.email = user.username; entry.email = user.username;
} else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) { } else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) {
@@ -104,12 +109,15 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
return entry; return entry;
} }
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> { private async getGroups(
force: boolean,
setFilter: [boolean, Set<string>]
): Promise<GroupEntry[]> {
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const query = this.createDirectoryQuery(this.syncConfig.groupFilter); const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
this.logService.info('Querying groups.'); this.logService.info("Querying groups.");
const roles = await this.apiGetMany('roles' + (query != null ? '?' + query : '')); const roles = await this.apiGetMany("roles" + (query != null ? "?" + query : ""));
roles.forEach(role => { roles.forEach((role) => {
const entry = this.buildGroup(role); const entry = this.buildGroup(role);
if (entry != null && !this.filterOutResult(setFilter, entry.name)) { if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
entries.push(entry); entries.push(entry);
@@ -125,7 +133,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
entry.name = group.name; entry.name = group.name;
if (this.allUsers != null) { if (this.allUsers != null) {
this.allUsers.forEach(user => { this.allUsers.forEach((user) => {
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) { if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
entry.userMemberExternalIds.add(user.id); entry.userMemberExternalIds.add(user.id);
} }
@@ -136,17 +144,21 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
} }
private async getAccessToken() { private async getAccessToken() {
const response = await fetch(`https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`, { const response = await fetch(
method: 'POST', `https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`,
{
method: "POST",
headers: new Headers({ headers: new Headers({
'Authorization': 'Basic ' + btoa(this.dirConfig.clientId + ':' + this.dirConfig.clientSecret), Authorization:
'Content-Type': 'application/json; charset=utf-8', "Basic " + btoa(this.dirConfig.clientId + ":" + this.dirConfig.clientSecret),
'Accept': 'application/json', "Content-Type": "application/json; charset=utf-8",
Accept: "application/json",
}), }),
body: JSON.stringify({ body: JSON.stringify({
grant_type: 'client_credentials', grant_type: "client_credentials",
}), }),
}); }
);
if (response.status === 200) { if (response.status === 200) {
const responseJson = await response.json(); const responseJson = await response.json();
if (responseJson.access_token != null) { if (responseJson.access_token != null) {
@@ -158,10 +170,10 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
private async apiGetCall(url: string): Promise<any> { private async apiGetCall(url: string): Promise<any> {
const req: RequestInit = { const req: RequestInit = {
method: 'GET', method: "GET",
headers: new Headers({ headers: new Headers({
Authorization: 'bearer:' + this.accessToken, Authorization: "bearer:" + this.accessToken,
Accept: 'application/json', Accept: "application/json",
}), }),
}; };
const response = await fetch(new Request(url, req)); const response = await fetch(new Request(url, req));
@@ -173,14 +185,16 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
} }
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> { private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
const url = endpoint.indexOf('https://') === 0 ? endpoint : const url =
`https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`; endpoint.indexOf("https://") === 0
? endpoint
: `https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`;
const response = await this.apiGetCall(url); const response = await this.apiGetCall(url);
if (response == null || response.status == null || response.data == null) { if (response == null || response.status == null || response.data == null) {
return currentData; return currentData;
} }
if (response.status.code !== 200) { if (response.status.code !== 200) {
throw new Error('API call failed.'); throw new Error("API call failed.");
} }
currentData = currentData.concat(response.data); currentData = currentData.concat(response.data);
if (response.pagination == null || response.pagination.next_link == null) { if (response.pagination == null || response.pagination.next_link == null) {
@@ -190,6 +204,6 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
} }
private validEmailAddress(email: string) { private validEmailAddress(email: string) {
return email != null && email !== '' && ValidEmailRegex.test(email); return email != null && email !== "" && ValidEmailRegex.test(email);
} }
} }

View File

@@ -1,45 +1,50 @@
import { DirectoryType } from '../enums/directoryType'; import { DirectoryType } from "../enums/directoryType";
import { GroupEntry } from '../models/groupEntry'; import { GroupEntry } from "../models/groupEntry";
import { SyncConfiguration } from '../models/syncConfiguration'; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from '../models/userEntry'; import { UserEntry } from "../models/userEntry";
import { OrganizationImportRequest } from 'jslib-common/models/request/organizationImportRequest'; import { OrganizationImportRequest } from "jslib-common/models/request/organizationImportRequest";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { AzureDirectoryService } from './azure-directory.service'; import { AzureDirectoryService } from "./azure-directory.service";
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from './directory.service'; import { IDirectoryService } 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'; import { OneLoginDirectoryService } from "./onelogin-directory.service";
export class SyncService { export class SyncService {
private dirType: DirectoryType; private dirType: DirectoryType;
constructor(private configurationService: ConfigurationService, private logService: LogService, constructor(
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService, private configurationService: ConfigurationService,
private messagingService: MessagingService, private i18nService: I18nService, private logService: LogService,
private environmentService: EnvironmentService) { } private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private messagingService: MessagingService,
private i18nService: I18nService,
private environmentService: EnvironmentService
) {}
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
this.dirType = await this.configurationService.getDirectoryType(); this.dirType = await this.configurationService.getDirectoryType();
if (this.dirType == null) { if (this.dirType == null) {
throw new Error('No directory configured.'); throw new Error("No directory configured.");
} }
const directoryService = this.getDirectoryService(); const directoryService = this.getDirectoryService();
if (directoryService == null) { if (directoryService == null) {
throw new Error('Cannot load directory service.'); throw new Error("Cannot load directory service.");
} }
const syncConfig = await this.configurationService.getSync(); const syncConfig = await this.configurationService.getSync();
@@ -47,9 +52,12 @@ export class SyncService {
const startingUserDelta = await this.configurationService.getUserDeltaToken(); const startingUserDelta = await this.configurationService.getUserDeltaToken();
const now = new Date(); const now = new Date();
this.messagingService.send('dirSyncStarted'); this.messagingService.send("dirSyncStarted");
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 = this.filterUnsupportedUsers(entries[1]); let users = this.filterUnsupportedUsers(entries[1]);
@@ -59,32 +67,48 @@ export class SyncService {
users = this.removeDuplicateUsers(users); users = this.removeDuplicateUsers(users);
if (test || (!syncConfig.overwriteExisting && if (
(groups == null || groups.length === 0) && (users == null || users.length === 0))) { test ||
(!syncConfig.overwriteExisting &&
(groups == null || groups.length === 0) &&
(users == null || users.length === 0))
) {
if (!test) { if (!test) {
await this.saveSyncTimes(syncConfig, now); await this.saveSyncTimes(syncConfig, now);
} }
this.messagingService.send('dirSyncCompleted', { successfully: true }); this.messagingService.send("dirSyncCompleted", { successfully: true });
return [groups, users]; return [groups, users];
} }
const req = this.buildRequest(groups, users, syncConfig.removeDisabled, syncConfig.overwriteExisting, syncConfig.largeImport); const req = this.buildRequest(
groups,
users,
syncConfig.removeDisabled,
syncConfig.overwriteExisting,
syncConfig.largeImport
);
const reqJson = JSON.stringify(req); const reqJson = JSON.stringify(req);
const orgId = await this.configurationService.getOrganizationId(); const orgId = await this.configurationService.getOrganizationId();
if (orgId == null) { if (orgId == null) {
throw new Error('Organization not set.'); throw new Error("Organization not set.");
} }
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes // TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
let hashLegacy: string = null; let hashLegacy: string = null;
const hashBuffLegacy = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + reqJson, 'sha256'); const hashBuffLegacy = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + reqJson,
"sha256"
);
if (hashBuffLegacy != null) { if (hashBuffLegacy != null) {
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy); hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
} }
let hash: string = null; let hash: string = null;
const hashBuff = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + orgId + reqJson, 'sha256'); const hashBuff = await this.cryptoFunctionService.hash(
this.environmentService.getApiUrl() + orgId + reqJson,
"sha256"
);
if (hashBuff != null) { if (hashBuff != null) {
hash = Utils.fromBufferToB64(hashBuff); hash = Utils.fromBufferToB64(hashBuff);
} }
@@ -99,7 +123,7 @@ export class SyncService {
} }
await this.saveSyncTimes(syncConfig, now); await this.saveSyncTimes(syncConfig, now);
this.messagingService.send('dirSyncCompleted', { successfully: true }); this.messagingService.send("dirSyncCompleted", { successfully: true });
return [groups, users]; return [groups, users];
} catch (e) { } catch (e) {
if (!test) { if (!test) {
@@ -107,7 +131,7 @@ export class SyncService {
await this.configurationService.saveUserDeltaToken(startingUserDelta); await this.configurationService.saveUserDeltaToken(startingUserDelta);
} }
this.messagingService.send('dirSyncCompleted', { successfully: false }); this.messagingService.send("dirSyncCompleted", { successfully: false });
throw e; throw e;
} }
} }
@@ -120,7 +144,7 @@ export class SyncService {
// UserEntrys with the same email are ignored if their properties are the same // UserEntrys with the same email are ignored if their properties are the same
// UserEntrys with the same email but different properties will throw an error, unless they are all in a deleted state. // UserEntrys with the same email but different properties will throw an error, unless they are all in a deleted state.
users.forEach(u => { users.forEach((u) => {
if (processedActiveUsers.has(u.email)) { if (processedActiveUsers.has(u.email)) {
if (processedActiveUsers.get(u.email) !== JSON.stringify(u)) { if (processedActiveUsers.get(u.email) !== JSON.stringify(u)) {
duplicateEmails.push(u.email); duplicateEmails.push(u.email);
@@ -143,17 +167,20 @@ export class SyncService {
}); });
if (duplicateEmails.length > 0) { if (duplicateEmails.length > 0) {
const emailsMessage = duplicateEmails.length < 4 ? const emailsMessage =
duplicateEmails.join('\n') : duplicateEmails.length < 4
duplicateEmails.slice(0, 3).join('\n') + '\n' + this.i18nService.t('andMore', `${duplicateEmails.length - 3}`); ? duplicateEmails.join("\n")
throw new Error(this.i18nService.t('duplicateEmails') + '\n' + emailsMessage); : duplicateEmails.slice(0, 3).join("\n") +
"\n" +
this.i18nService.t("andMore", `${duplicateEmails.length - 3}`);
throw new Error(this.i18nService.t("duplicateEmails") + "\n" + emailsMessage);
} }
return uniqueUsers; return uniqueUsers;
} }
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] { private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
return users == null ? null : users.filter(u => u.email?.length <= 256); return users == null ? null : users.filter((u) => u.email?.length <= 256);
} }
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> { private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
@@ -162,9 +189,9 @@ export class SyncService {
return allUsers; 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);
childUsers.forEach(id => group.userMemberExternalIds.add(id)); childUsers.forEach((id) => group.userMemberExternalIds.add(id));
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]); allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
} }
return allUsers; return allUsers;
@@ -173,31 +200,56 @@ export class SyncService {
private getDirectoryService(): IDirectoryService { private getDirectoryService(): IDirectoryService {
switch (this.dirType) { switch (this.dirType) {
case DirectoryType.GSuite: case DirectoryType.GSuite:
return new GSuiteDirectoryService(this.configurationService, this.logService, this.i18nService); return new GSuiteDirectoryService(
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.AzureActiveDirectory: case DirectoryType.AzureActiveDirectory:
return new AzureDirectoryService(this.configurationService, this.logService, this.i18nService); return new AzureDirectoryService(
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.Ldap: case DirectoryType.Ldap:
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: case DirectoryType.OneLogin:
return new OneLoginDirectoryService(this.configurationService, this.logService, this.i18nService); return new OneLoginDirectoryService(
this.configurationService,
this.logService,
this.i18nService
);
default: default:
return null; return null;
} }
} }
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean, overwriteExisting: boolean, private buildRequest(
largeImport: boolean = false) { groups: GroupEntry[],
users: UserEntry[],
removeDisabled: boolean,
overwriteExisting: boolean,
largeImport: boolean = false
) {
return new OrganizationImportRequest({ return new OrganizationImportRequest({
groups: (groups ?? []).map(g => { groups: (groups ?? []).map((g) => {
return { return {
name: g.name, name: g.name,
externalId: g.externalId, externalId: g.externalId,
memberExternalIds: Array.from(g.userMemberExternalIds), memberExternalIds: Array.from(g.userMemberExternalIds),
}; };
}), }),
users: (users ?? []).map(u => { users: (users ?? []).map((u) => {
return { return {
email: u.email, email: u.email,
externalId: u.externalId, externalId: u.externalId,

View File

@@ -1,15 +1,19 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { SyncService } from './services/sync.service'; import { SyncService } from "./services/sync.service";
import { Entry } from './models/entry'; import { Entry } from "./models/entry";
import { LdapConfiguration } from './models/ldapConfiguration'; import { LdapConfiguration } from "./models/ldapConfiguration";
import { SimResult } from './models/simResult'; import { SimResult } from "./models/simResult";
import { SyncConfiguration } from './models/syncConfiguration'; import { SyncConfiguration } from "./models/syncConfiguration";
import { UserEntry } from './models/userEntry'; import { UserEntry } from "./models/userEntry";
export class ConnectorUtils { export class ConnectorUtils {
static async simulate(syncService: SyncService, i18nService: I18nService, sinceLast: boolean): Promise<SimResult> { static async simulate(
syncService: SyncService,
i18nService: I18nService,
sinceLast: boolean
): Promise<SimResult> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const simResult = new SimResult(); const simResult = new SimResult();
try { try {
@@ -23,7 +27,7 @@ export class ConnectorUtils {
} catch (e) { } catch (e) {
simResult.groups = null; simResult.groups = null;
simResult.users = null; simResult.users = null;
reject(e || i18nService.t('syncError')); reject(e || i18nService.t("syncError"));
return; return;
} }
@@ -46,7 +50,7 @@ export class ConnectorUtils {
continue; continue;
} }
const anyG = (g as any); const anyG = g as any;
anyG.users = []; anyG.users = [];
for (const uid of g.userMemberExternalIds) { for (const uid of g.userMemberExternalIds) {
if (userMap.has(uid)) { if (userMap.has(uid)) {
@@ -65,20 +69,20 @@ export class ConnectorUtils {
static adjustConfigForSave(ldap: LdapConfiguration, sync: SyncConfiguration) { static adjustConfigForSave(ldap: LdapConfiguration, sync: SyncConfiguration) {
if (ldap.ad) { if (ldap.ad) {
sync.creationDateAttribute = 'whenCreated'; sync.creationDateAttribute = "whenCreated";
sync.revisionDateAttribute = 'whenChanged'; sync.revisionDateAttribute = "whenChanged";
sync.emailPrefixAttribute = 'sAMAccountName'; sync.emailPrefixAttribute = "sAMAccountName";
sync.memberAttribute = 'member'; sync.memberAttribute = "member";
sync.userObjectClass = 'person'; sync.userObjectClass = "person";
sync.groupObjectClass = 'group'; sync.groupObjectClass = "group";
sync.userEmailAttribute = 'mail'; sync.userEmailAttribute = "mail";
sync.groupNameAttribute = 'name'; sync.groupNameAttribute = "name";
if (sync.groupPath == null) { if (sync.groupPath == null) {
sync.groupPath = 'CN=Users'; sync.groupPath = "CN=Users";
} }
if (sync.userPath == null) { if (sync.userPath == null) {
sync.userPath = 'CN=Users'; sync.userPath = "CN=Users";
} }
} }
@@ -93,8 +97,9 @@ export class ConnectorUtils {
private static sortEntries(arr: Entry[], i18nService: I18nService) { private static sortEntries(arr: Entry[], i18nService: I18nService) {
arr.sort((a, b) => { arr.sort((a, b) => {
return i18nService.collator ? i18nService.collator.compare(a.displayName, b.displayName) : return i18nService.collator
a.displayName.localeCompare(b.displayName); ? i18nService.collator.compare(a.displayName, b.displayName)
: a.displayName.localeCompare(b.displayName);
}); });
} }
} }

View File

@@ -12,28 +12,15 @@
"types": [], "types": [],
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"tldjs": [ "tldjs": ["jslib/src/misc/tldjs.noop"],
"jslib/src/misc/tldjs.noop" "jslib-common/*": ["jslib/common/src/*"],
], "jslib-angular/*": ["jslib/angular/src/*"],
"jslib-common/*": [ "jslib-electron/*": ["jslib/electron/src/*"],
"jslib/common/src/*" "jslib-node/*": ["jslib/node/src/*"]
],
"jslib-angular/*": [
"jslib/angular/src/*"
],
"jslib-electron/*": [
"jslib/electron/src/*"
],
"jslib-node/*": [
"jslib/node/src/*"
]
} }
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"preserveWhitespaces": true "preserveWhitespaces": true
}, },
"include": [ "include": ["src", "src-cli"]
"src",
"src-cli"
]
} }

View File

@@ -38,7 +38,6 @@
"object-literal-sort-keys": false, "object-literal-sort-keys": false,
"object-literal-shorthand": [true, "never"], "object-literal-shorthand": [true, "never"],
"prefer-for-of": false, "prefer-for-of": false,
"quotemark": [ true, "single" ],
"whitespace": [ "whitespace": [
true, true,
"check-branch", "check-branch",

View File

@@ -1,45 +1,43 @@
const path = require('path'); const path = require("path");
const webpack = require('webpack'); const webpack = require("webpack");
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require("copy-webpack-plugin");
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require("webpack-node-externals");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
if (process.env.NODE_ENV == null) { if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = "development";
} }
const ENV = process.env.ENV = process.env.NODE_ENV; const ENV = (process.env.ENV = process.env.NODE_ENV);
const moduleRules = [ const moduleRules = [
{ {
test: /\.ts$/, test: /\.ts$/,
enforce: 'pre', enforce: "pre",
loader: 'tslint-loader', loader: "tslint-loader",
}, },
{ {
test: /\.ts$/, test: /\.ts$/,
use: 'ts-loader', use: "ts-loader",
exclude: path.resolve(__dirname, 'node_modules'), exclude: path.resolve(__dirname, "node_modules"),
}, },
{ {
test: /\.node$/, test: /\.node$/,
loader: 'node-loader', loader: "node-loader",
}, },
]; ];
const plugins = [ const plugins = [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [{ from: "./src/locales", to: "locales" }],
{ from: './src/locales', to: 'locales' },
],
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.BWCLI_ENV': JSON.stringify(ENV), "process.env.BWCLI_ENV": JSON.stringify(ENV),
}), }),
new webpack.BannerPlugin({ new webpack.BannerPlugin({
banner: '#!/usr/bin/env node', banner: "#!/usr/bin/env node",
raw: true raw: true,
}), }),
new webpack.IgnorePlugin({ new webpack.IgnorePlugin({
resourceRegExp: /^encoding$/, resourceRegExp: /^encoding$/,
@@ -49,27 +47,27 @@ const plugins = [
const config = { const config = {
mode: ENV, mode: ENV,
target: 'node', target: "node",
devtool: ENV === 'development' ? 'eval-source-map' : 'source-map', devtool: ENV === "development" ? "eval-source-map" : "source-map",
node: { node: {
__dirname: false, __dirname: false,
__filename: false, __filename: false,
}, },
entry: { entry: {
'bwdc': './src/bwdc.ts', bwdc: "./src/bwdc.ts",
}, },
optimization: { optimization: {
minimize: false, minimize: false,
}, },
resolve: { resolve: {
extensions: ['.ts', '.js', '.json'], extensions: [".ts", ".js", ".json"],
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
symlinks: false, symlinks: false,
modules: [path.resolve('node_modules')], modules: [path.resolve("node_modules")],
}, },
output: { output: {
filename: '[name].js', filename: "[name].js",
path: path.resolve(__dirname, 'build-cli'), path: path.resolve(__dirname, "build-cli"),
}, },
module: { rules: moduleRules }, module: { rules: moduleRules },
plugins: plugins, plugins: plugins,

View File

@@ -1,45 +1,45 @@
const path = require('path'); const path = require("path");
const { merge } = require('webpack-merge'); const { merge } = require("webpack-merge");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require("webpack-node-externals");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const common = { const common = {
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, test: /\.ts$/,
enforce: 'pre', enforce: "pre",
loader: 'tslint-loader', loader: "tslint-loader",
}, },
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules\/(?!(@bitwarden)\/).*/, exclude: /node_modules\/(?!(@bitwarden)\/).*/,
}, },
], ],
}, },
plugins: [], plugins: [],
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js'], extensions: [".tsx", ".ts", ".js"],
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
}, },
output: { output: {
filename: '[name].js', filename: "[name].js",
path: path.resolve(__dirname, 'build'), path: path.resolve(__dirname, "build"),
}, },
}; };
const main = { const main = {
mode: 'production', mode: "production",
target: 'electron-main', target: "electron-main",
node: { node: {
__dirname: false, __dirname: false,
__filename: false, __filename: false,
}, },
entry: { entry: {
'main': './src/main.ts', main: "./src/main.ts",
}, },
optimization: { optimization: {
minimize: false, minimize: false,
@@ -48,7 +48,7 @@ const main = {
rules: [ rules: [
{ {
test: /\.node$/, test: /\.node$/,
loader: 'node-loader', loader: "node-loader",
}, },
], ],
}, },
@@ -56,9 +56,9 @@ const main = {
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [
'./src/package.json', "./src/package.json",
{ from: './src/images', to: 'images' }, { from: "./src/images", to: "images" },
{ from: './src/locales', to: 'locales' }, { from: "./src/locales", to: "locales" },
], ],
}), }),
], ],

View File

@@ -1,55 +1,55 @@
const path = require('path'); 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 MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { AngularWebpackPlugin } = require('@ngtools/webpack'); const { AngularWebpackPlugin } = require("@ngtools/webpack");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const common = { const common = {
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, test: /\.ts$/,
enforce: 'pre', enforce: "pre",
loader: 'tslint-loader', loader: "tslint-loader",
}, },
{ {
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '@ngtools/webpack', loader: "@ngtools/webpack",
}, },
{ {
test: /\.(jpe?g|png|gif|svg)$/i, test: /\.(jpe?g|png|gif|svg)$/i,
exclude: /.*(fontawesome-webfont)\.svg/, exclude: /.*(fontawesome-webfont)\.svg/,
generator: { generator: {
filename: 'images/[name].[ext]', filename: "images/[name].[ext]",
}, },
type: 'asset/resource', type: "asset/resource",
}, },
], ],
}, },
plugins: [], plugins: [],
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'], extensions: [".tsx", ".ts", ".js", ".json"],
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
symlinks: false, symlinks: false,
modules: [path.resolve('node_modules')], modules: [path.resolve("node_modules")],
}, },
output: { output: {
filename: '[name].js', filename: "[name].js",
path: path.resolve(__dirname, 'build'), path: path.resolve(__dirname, "build"),
}, },
}; };
const renderer = { const renderer = {
mode: 'production', mode: "production",
devtool: false, devtool: false,
target: 'electron-renderer', target: "electron-renderer",
node: { node: {
__dirname: false, __dirname: false,
}, },
entry: { entry: {
'app/main': './src/app/main.ts', "app/main": "./src/app/main.ts",
}, },
optimization: { optimization: {
minimize: false, minimize: false,
@@ -57,9 +57,9 @@ const renderer = {
cacheGroups: { cacheGroups: {
commons: { commons: {
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
name: 'app/vendor', name: "app/vendor",
chunks: (chunk) => { chunks: (chunk) => {
return chunk.name === 'app/main'; return chunk.name === "app/main";
}, },
}, },
}, },
@@ -69,15 +69,15 @@ const renderer = {
rules: [ rules: [
{ {
test: /\.(html)$/, test: /\.(html)$/,
loader: 'html-loader', loader: "html-loader",
}, },
{ {
test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
exclude: /loading.svg/, exclude: /loading.svg/,
generator: { generator: {
filename: 'fonts/[name].[ext]', filename: "fonts/[name].[ext]",
}, },
type: 'asset/resource', type: "asset/resource",
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
@@ -85,11 +85,11 @@ const renderer = {
{ {
loader: MiniCssExtractPlugin.loader, loader: MiniCssExtractPlugin.loader,
options: { options: {
publicPath: '../', publicPath: "../",
}, },
}, },
'css-loader', "css-loader",
'sass-loader', "sass-loader",
], ],
}, },
// Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560
@@ -101,25 +101,27 @@ const renderer = {
}, },
plugins: [ plugins: [
new AngularWebpackPlugin({ new AngularWebpackPlugin({
tsConfigPath: 'tsconfig.json', tsConfigPath: "tsconfig.json",
entryModule: 'src/app/app.module#AppModule', entryModule: "src/app/app.module#AppModule",
sourceMap: true, sourceMap: true,
}), }),
// ref: https://github.com/angular/angular/issues/20357 // ref: https://github.com/angular/angular/issues/20357
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, new webpack.ContextReplacementPlugin(
path.resolve(__dirname, './src')), /\@angular(\\|\/)core(\\|\/)fesm5/,
path.resolve(__dirname, "./src")
),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: './src/index.html', template: "./src/index.html",
filename: 'index.html', filename: "index.html",
chunks: ['app/vendor', 'app/main'], chunks: ["app/vendor", "app/main"],
}), }),
new webpack.SourceMapDevToolPlugin({ new webpack.SourceMapDevToolPlugin({
include: ['app/main.js'], include: ["app/main.js"],
}), }),
new webpack.DefinePlugin({ 'global.GENTLY': false }), new webpack.DefinePlugin({ "global.GENTLY": false }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '[name].[contenthash].css', filename: "[name].[contenthash].css",
chunkFilename: '[id].[contenthash].css', chunkFilename: "[id].[contenthash].css",
}), }),
], ],
}; };