mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-24 04:04:29 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8072c523cc | ||
|
|
f96429a47b | ||
|
|
5e64dc9262 | ||
|
|
9c7cd943b3 | ||
|
|
7cf3166169 | ||
|
|
9bdb77a573 | ||
|
|
3b8ee5ec0d | ||
|
|
6e7e09064f | ||
|
|
dfcb450a8a | ||
|
|
b192c34c15 | ||
|
|
f813dbb690 | ||
|
|
16deafca76 | ||
|
|
647b087fa7 | ||
|
|
4bd1387b83 | ||
|
|
a6aafe7593 |
@@ -47,7 +47,7 @@ We provide detailed documentation and examples for using the Directory Connector
|
||||
|
||||
**Requirements**
|
||||
|
||||
- [Node.js](https://nodejs.org/)
|
||||
- [Node.js](https://nodejs.org) v14
|
||||
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
||||
|
||||
**Run the app**
|
||||
|
||||
2
jslib
2
jslib
Submodule jslib updated: 55a9ea9e18...c70c8ecc24
@@ -2,23 +2,23 @@
|
||||
"name": "bitwarden-directory-connector",
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"description": "Sync your user directory to your Bitwarden organization.",
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.4",
|
||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"license": "GPL-3.0",
|
||||
"main": "main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bitwarden/directory-connector"
|
||||
"type": "git",
|
||||
"url": "https://github.com/bitwarden/directory-connector"
|
||||
},
|
||||
"bin": {
|
||||
"bwdc": "../build-cli/bwdc.js"
|
||||
"bwdc": "../build-cli/bwdc.js"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": "../build-cli/**/*"
|
||||
"assets": "../build-cli/**/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"keytar": "7.6.0"
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"keytar": "7.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,15 @@
|
||||
<div class="form-group">
|
||||
<div class="row-main">
|
||||
<label for="client_secret">{{'clientSecret' | i18n}}</label>
|
||||
<input id="client_secret" name="ClientSecret"
|
||||
<div class="input-group">
|
||||
<input type="{{showSecret ? 'text' : 'password'}}" id="client_secret" name="ClientSecret"
|
||||
[(ngModel)]="clientSecret" class="form-control">
|
||||
<div class="input-group-append">
|
||||
<button 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
|
||||
@@ -29,6 +29,7 @@ export class ApiKeyComponent {
|
||||
|
||||
formPromise: Promise<any>;
|
||||
successRoute = '/tabs/dashboard';
|
||||
showSecret: boolean = false;
|
||||
|
||||
constructor(private authService: AuthService, private apiKeyService: ApiKeyService, private router: Router,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
@@ -77,4 +78,8 @@ export class ApiKeyComponent {
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
toggleSecret() {
|
||||
this.showSecret = !this.showSecret;
|
||||
document.getElementById('client_secret').focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ const cryptoService = new CryptoService(storageService, secureStorageService, cr
|
||||
platformUtilsService, logService);
|
||||
const appIdService = new AppIdService(storageService);
|
||||
const tokenService = new TokenService(storageService);
|
||||
const apiService = new ApiService(tokenService, platformUtilsService, refreshTokenCallback,
|
||||
const environmentService = new EnvironmentService(storageService);
|
||||
const apiService = new ApiService(tokenService, platformUtilsService, environmentService, refreshTokenCallback,
|
||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||
const environmentService = new EnvironmentService(apiService, storageService, null);
|
||||
const userService = new UserService(tokenService, storageService);
|
||||
const apiKeyService = new ApiKeyService(tokenService, storageService);
|
||||
const containerService = new ContainerService(cryptoService);
|
||||
@@ -80,7 +80,7 @@ const authService = new AuthService(cryptoService, apiService, userService, toke
|
||||
i18nService, platformUtilsService, messagingService, null, logService, apiKeyService, false);
|
||||
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
||||
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
||||
messagingService, i18nService);
|
||||
messagingService, i18nService, environmentService);
|
||||
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, null);
|
||||
const policyService = new PolicyService(userService, storageService);
|
||||
|
||||
|
||||
@@ -121,8 +121,15 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">{{'password' | i18n}}</label>
|
||||
<input type="password" class="form-control" id="password" name="Password"
|
||||
<div class="input-group">
|
||||
<input type="{{showLdapPassword ? 'text' : 'password'}}" class="form-control" id="password" name="Password"
|
||||
[(ngModel)]="ldap.password">
|
||||
<div class="input-group-append">
|
||||
<button 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +146,15 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="secretKey">{{'secretKey' | i18n}}</label>
|
||||
<input type="text" class="form-control" id="secretKey" name="SecretKey" [(ngModel)]="azure.key">
|
||||
<div class="input-group">
|
||||
<input type="{{showAzureKey ? 'text' : 'password'}}" class="form-control" id="secretKey" name="SecretKey"
|
||||
[(ngModel)]="azure.key">
|
||||
<div class="input-group-append">
|
||||
<button 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [hidden]="directory != directoryType.Okta">
|
||||
@@ -150,8 +165,15 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="oktaToken">{{'token' | i18n}}</label>
|
||||
<input type="text" class="form-control" id="oktaToken" name="OktaToken"
|
||||
<div class="input-group">
|
||||
<input type="{{showOktaKey ? 'text' : 'password'}}" class="form-control" id="oktaToken" name="OktaToken"
|
||||
[(ngModel)]="okta.token">
|
||||
<div class="input-group-append">
|
||||
<button 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [hidden]="directory != directoryType.OneLogin">
|
||||
@@ -162,8 +184,15 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label>
|
||||
<input type="text" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
|
||||
<div class="input-group">
|
||||
<input type="{{showOneLoginSecret ? 'text' : 'password'}}" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
|
||||
[(ngModel)]="oneLogin.clientSecret">
|
||||
<div class="input-group-append">
|
||||
<button 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="oneLoginRegion">{{'region' | i18n}}</label>
|
||||
|
||||
@@ -38,6 +38,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
oneLogin = new OneLoginConfiguration();
|
||||
sync = new SyncConfiguration();
|
||||
directoryOptions: any[];
|
||||
showLdapPassword: boolean = false;
|
||||
showAzureKey: boolean = false;
|
||||
showOktaKey: boolean = false;
|
||||
showOneLoginSecret: boolean = false;
|
||||
|
||||
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
||||
@@ -126,4 +130,24 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
filePicker.type = 'file';
|
||||
filePicker.value = '';
|
||||
}
|
||||
|
||||
toggleLdapPassword() {
|
||||
this.showLdapPassword = !this.showLdapPassword;
|
||||
document.getElementById('password').focus();
|
||||
}
|
||||
|
||||
toggleAzureKey() {
|
||||
this.showAzureKey = !this.showAzureKey;
|
||||
document.getElementById('secretKey').focus();
|
||||
}
|
||||
|
||||
toggleOktaKey() {
|
||||
this.showOktaKey = !this.showOktaKey;
|
||||
document.getElementById('oktaToken').focus();
|
||||
}
|
||||
|
||||
toggleOneLoginSecret() {
|
||||
this.showOneLoginSecret = !this.showOneLoginSecret;
|
||||
document.getElementById('oneLoginClientSecret').focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +91,9 @@ export class Main {
|
||||
this.appIdService = new AppIdService(this.storageService);
|
||||
this.tokenService = new TokenService(this.storageService);
|
||||
this.messagingService = new NoopMessagingService();
|
||||
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.refreshTokenCallback,
|
||||
async (expired: boolean) => await this.logout());
|
||||
this.environmentService = new EnvironmentService(this.apiService, this.storageService, null);
|
||||
this.environmentService = new EnvironmentService(this.storageService);
|
||||
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService,
|
||||
this.refreshTokenCallback, async (expired: boolean) => await this.logout());
|
||||
this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
|
||||
this.userService = new UserService(this.tokenService, this.storageService);
|
||||
this.containerService = new ContainerService(this.cryptoService);
|
||||
@@ -103,7 +103,7 @@ export class Main {
|
||||
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.apiService, this.messagingService, this.i18nService, this.environmentService);
|
||||
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, null);
|
||||
this.program = new Program(this);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "bitwarden-directory-connector",
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"description": "Sync your user directory to your Bitwarden organization.",
|
||||
"version": "2.9.3",
|
||||
"version": "2.9.4",
|
||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"license": "GPL-3.0",
|
||||
|
||||
@@ -118,3 +118,27 @@ ul.testing-list {
|
||||
}
|
||||
}
|
||||
|
||||
.btn[class*="btn-outline-"] {
|
||||
&:not(:hover) {
|
||||
border-color: $secondary;
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
color: $text-muted;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), .5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||
|
||||
@@ -18,10 +19,10 @@ export async function refreshToken(apiKeyService: ApiKeyService, authService: Au
|
||||
}
|
||||
|
||||
export class ApiService extends ApiServiceBase {
|
||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
|
||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
||||
customUserAgent: string = null) {
|
||||
super(tokenService, platformUtilsService, logoutCallback, customUserAgent);
|
||||
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||
}
|
||||
|
||||
doRefreshToken(): Promise<void> {
|
||||
|
||||
@@ -45,7 +45,7 @@ export class AuthService extends AuthServiceBase {
|
||||
const appId = await this.appIdService.getAppId();
|
||||
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
||||
const request = new TokenRequest(null, null, [clientId, clientSecret], null,
|
||||
null, false, deviceRequest);
|
||||
null, false, null, deviceRequest);
|
||||
|
||||
const response = await this.apiService.postIdentityToken(request);
|
||||
const result = new AuthResult();
|
||||
|
||||
@@ -204,7 +204,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
||||
if (keyword === 'excludeadministrativeunit' || keyword === 'includeadministrativeunit') {
|
||||
for (const p of pieces) {
|
||||
const auMembers = await this.client
|
||||
.api(`https://graph.microsoft.com/beta/administrativeUnits/${p}/members`).get();
|
||||
.api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`).get();
|
||||
for (const auMember of auMembers.value) {
|
||||
if (auMember['@odata.type'] === '#microsoft.graph.group') {
|
||||
set.add(auMember.displayName.toLowerCase());
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||
|
||||
import { NodeApiService as NodeApiServiceBase } from 'jslib-node/services/nodeApi.service';
|
||||
|
||||
export class NodeApiService extends NodeApiServiceBase {
|
||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
|
||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
||||
customUserAgent: string = null) {
|
||||
super(tokenService, platformUtilsService, logoutCallback, customUserAgent);
|
||||
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||
}
|
||||
|
||||
doRefreshToken(): Promise<void> {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { OrganizationImportRequest } from 'jslib-common/models/request/organizat
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
@@ -27,7 +28,8 @@ export class SyncService {
|
||||
|
||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
||||
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService,
|
||||
private messagingService: MessagingService, private i18nService: I18nService) { }
|
||||
private messagingService: MessagingService, private i18nService: I18nService,
|
||||
private environmentService: EnvironmentService) { }
|
||||
|
||||
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||
this.dirType = await this.configurationService.getDirectoryType();
|
||||
@@ -83,12 +85,12 @@ export class SyncService {
|
||||
|
||||
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
|
||||
let hashLegacy: string = null;
|
||||
const hashBuffLegacy = await this.cryptoFunctionService.hash(this.apiService.apiBaseUrl + reqJson, 'sha256');
|
||||
const hashBuffLegacy = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + reqJson, 'sha256');
|
||||
if (hashBuffLegacy != null) {
|
||||
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
|
||||
}
|
||||
let hash: string = null;
|
||||
const hashBuff = await this.cryptoFunctionService.hash(this.apiService.apiBaseUrl + orgId + reqJson, 'sha256');
|
||||
const hashBuff = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + orgId + reqJson, 'sha256');
|
||||
if (hashBuff != null) {
|
||||
hash = Utils.fromBufferToB64(hashBuff);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user