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

Compare commits

...

4 Commits

Author SHA1 Message Date
Vincent Salucci
b90694d17a [Version] Bump to 2.9.3 (#138) 2021-06-28 12:52:48 -05:00
Matt Gibson
b1b0d858ca Error on duplicate emails (#136)
* Allow main debugging in development builds

* Early fail on attempting to sync multiple users with the same email

* Truncate duplicate list if greater than 3

* Revert "Allow main debugging in development builds"

This reverts commit 3b804dd959.

(cherry picked from commit 6e7e09064f)
2021-06-24 15:35:54 -04:00
Matt Gibson
326f11be19 update jslib 2021-06-22 16:14:22 -04:00
Matt Gibson
996364f2dd Refresh token with api key (#135)
* Do not persist client creds on logout

* Override refreshing token flow with re-authentication flow

* Update jslib

* PR review comments

(cherry picked from commit 647b087fa7)
2021-06-22 16:13:42 -04:00
11 changed files with 106 additions and 8 deletions

2
jslib

Submodule jslib updated: d7682cde3b...55a9ea9e18

View File

@@ -21,7 +21,6 @@ import { SyncService } from '../../services/sync.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { ValidationService } from 'jslib-angular/services/validation.service';
import { ApiService } from 'jslib-common/services/api.service';
import { ApiKeyService } from 'jslib-common/services/apiKey.service';
import { AppIdService } from 'jslib-common/services/appId.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
@@ -55,6 +54,7 @@ import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstra
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service';
import { ApiService, refreshToken } from '../../services/api.service';
import { AuthService } from '../../services/auth.service';
const logService = new ElectronLogService();
@@ -70,7 +70,7 @@ 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,
const apiService = new ApiService(tokenService, platformUtilsService, refreshTokenCallback,
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
const environmentService = new EnvironmentService(apiService, storageService, null);
const userService = new UserService(tokenService, storageService);
@@ -86,6 +86,10 @@ const policyService = new PolicyService(userService, storageService);
containerService.attachToWindow(window);
function refreshTokenCallback(): Promise<any> {
return refreshToken(apiKeyService, authService);
}
export function initFactory(): Function {
return async () => {
await environmentService.setUrlsFromStorage();

View File

@@ -9,6 +9,7 @@ import { ConfigurationService } from './services/configuration.service';
import { I18nService } from './services/i18n.service';
import { KeytarSecureStorageService } from './services/keytarSecureStorage.service';
import { LowdbStorageService } from './services/lowdbStorage.service';
import { NodeApiService } from './services/nodeApi.service';
import { SyncService } from './services/sync.service';
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service';
@@ -25,11 +26,11 @@ import { NoopMessagingService } from 'jslib-common/services/noopMessaging.servic
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { TokenService } from 'jslib-common/services/token.service';
import { UserService } from 'jslib-common/services/user.service';
import { NodeApiService } from 'jslib-node/services/nodeApi.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { Program } from './program';
import { refreshToken } from './services/api.service';
// tslint:disable-next-line
const packageJson = require('./package.json');
@@ -90,7 +91,7 @@ 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.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.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
@@ -117,6 +118,10 @@ export class Main {
await this.apiKeyService.clear();
}
refreshTokenCallback() {
return refreshToken(this.apiKeyService, this.authService);
}
private async init() {
await this.storageService.init();
this.containerService.attachToWindow(global);

View File

@@ -441,6 +441,19 @@
"sync": {
"message": "Sync"
},
"duplicateEmails": {
"message": "Emails must be unique. Multiple entries pulled with the following emails:",
"desription": "Error message displayed when duplicate email addresses are synced. Followed by a list of duplicate emails."
},
"andMore": {
"message": "and $NUMBER$ more...",
"placeholders": {
"NUMBER": {
"content": "$1",
"example": "10"
}
}
},
"ldapEncrypted": {
"message": "This server uses an encrypted connection"
},

View File

@@ -67,7 +67,7 @@ export class Main {
this.messagingMain.onMessage(message);
});
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector');
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null);
}
bootstrap() {

View File

@@ -2,7 +2,7 @@
"name": "bitwarden-directory-connector",
"productName": "Bitwarden Directory Connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "2.9.2",
"version": "2.9.3",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",

View File

@@ -0,0 +1,30 @@
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { ApiService as ApiServiceBase } from 'jslib-common/services/api.service';
export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) {
try {
const clientId = await apiKeyService.getClientId();
const clientSecret = await apiKeyService.getClientSecret();
if (clientId != null && clientSecret != null) {
await authService.logInApiKey(clientId, clientSecret);
}
} catch (e) {
return Promise.reject(e);
}
}
export class ApiService extends ApiServiceBase {
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService,
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null) {
super(tokenService, platformUtilsService, logoutCallback, customUserAgent);
}
doRefreshToken(): Promise<void> {
return this.refreshTokenCallback();
}
}

View File

@@ -36,6 +36,11 @@ export class AuthService extends AuthServiceBase {
return await super.logInApiKey(clientId, clientSecret);
}
async logOut(callback: Function) {
this.apiKeyService.clear();
super.logOut(callback);
}
private async organizationLogInHelper(clientId: string, clientSecret: string) {
const appId = await this.appIdService.getAppId();
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
@@ -49,7 +54,7 @@ export class AuthService extends AuthServiceBase {
const tokenResponse = response as IdentityTokenResponse;
result.resetMasterPassword = tokenResponse.resetMasterPassword;
await this.tokenService.setToken(tokenResponse.accessToken);
await this.apiKeyService.setInformation(clientId);
await this.apiKeyService.setInformation(clientId, clientSecret);
return result;
}

View File

@@ -15,6 +15,10 @@ export class KeytarSecureStorageService implements StorageService {
});
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
save(key: string, obj: any): Promise<any> {
return setPassword(this.serviceName, key, JSON.stringify(obj));
}

View File

@@ -0,0 +1,16 @@
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,
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null) {
super(tokenService, platformUtilsService, logoutCallback, customUserAgent);
}
doRefreshToken(): Promise<void> {
return this.refreshTokenCallback();
}
}

View File

@@ -55,6 +55,14 @@ export class SyncService {
this.flattenUsersToGroups(groups, groups);
}
const duplicateEmails = this.findDuplicateUserEmails(users);
if (duplicateEmails.length > 0) {
const emailsMessage = duplicateEmails.length < 4 ?
duplicateEmails.join('\n') :
duplicateEmails.slice(0, 3).join('\n') + '\n' + this.i18nService.t('andMore', `${duplicateEmails.length - 3}`);
throw new Error(this.i18nService.t('duplicateEmails') + '\n' + emailsMessage);
}
if (test || (!syncConfig.overwriteExisting &&
(groups == null || groups.length === 0) && (users == null || users.length === 0))) {
if (!test) {
@@ -108,6 +116,19 @@ export class SyncService {
}
}
private findDuplicateUserEmails(users: UserEntry[]) {
const duplicatedEmails = new Array<string>();
users.reduce((agg, user) => {
if (agg.includes(user.email) && !duplicatedEmails.includes(user.email)) {
duplicatedEmails.push(user.email);
} else {
agg.push(user.email);
}
return agg;
}, new Array<string>());
return duplicatedEmails;
}
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
return users == null ? null : users.filter(u => u.email?.length <= 256);
}