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

sync logic

This commit is contained in:
Kyle Spearrin
2018-05-02 22:00:44 -04:00
parent 3981cb95fc
commit 95d98181fc
8 changed files with 114 additions and 59 deletions

View File

@@ -71,7 +71,7 @@ const containerService = new ContainerService(cryptoService, platformUtilsServic
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
i18nService, platformUtilsService, messagingService, false);
const configurationService = new ConfigurationService(storageService, secureStorageService);
const syncSevrice = new SyncService(configurationService, logService);
const syncSevrice = new SyncService(configurationService, logService, cryptoFunctionService, apiService);
const analytics = new Analytics(window, () => true, platformUtilsService, storageService, appIdService);
containerService.attachToWindow(window);

View File

@@ -28,7 +28,7 @@ export class DashboardComponent {
constructor(private i18nService: I18nService, private syncService: SyncService) { }
async sync() {
this.syncPromise = this.syncService.sync(false, true);
this.syncPromise = this.syncService.sync(false, false);
await this.syncPromise;
}
@@ -41,7 +41,7 @@ export class DashboardComponent {
this.simPromise = new Promise(async (resolve, reject) => {
try {
const result = await this.syncService.sync(!this.simSinceLast, false);
const result = await this.syncService.sync(!this.simSinceLast, true);
this.simUsers = result[1];
this.simGroups = result[0];
} catch (e) {
@@ -51,7 +51,7 @@ export class DashboardComponent {
const userMap = new Map<string, UserEntry>();
if (this.simUsers != null) {
this.sort(this.simUsers);
this.simUsers.forEach((u) => {
for (const u of this.simUsers) {
userMap.set(u.externalId, u);
if (u.deleted) {
this.simDeletedUsers.push(u);
@@ -60,29 +60,29 @@ export class DashboardComponent {
} else {
this.simEnabledUsers.push(u);
}
});
}
}
if (userMap.size > 0 && this.simGroups != null) {
this.sort(this.simGroups);
this.simGroups.forEach((g) => {
for (const g of this.simGroups) {
if (g.userMemberExternalIds == null) {
return;
}
g.userMemberExternalIds.forEach((uid) => {
for (const uid of g.userMemberExternalIds) {
if (userMap.has(uid)) {
if ((g as any).users == null) {
(g as any).users = [];
}
(g as any).users.push(userMap.get(uid));
}
});
}
if ((g as any).users != null) {
this.sort((g as any).users);
}
});
}
}
resolve();
});

View File

@@ -28,7 +28,7 @@ export class AzureDirectoryService implements DirectoryService {
this.init();
}
async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> {
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.AzureActiveDirectory) {
return;
@@ -47,7 +47,7 @@ export class AzureDirectoryService implements DirectoryService {
let users: UserEntry[];
if (this.syncConfig.users) {
users = await this.getUsers(force);
users = await this.getUsers(force, !test);
}
let groups: GroupEntry[];
@@ -57,7 +57,7 @@ export class AzureDirectoryService implements DirectoryService {
const groupForce = force ||
(users != null && users.filter((u) => !u.deleted && !u.disabled).length > 0);
groups = await this.getGroups(groupForce, setFilter);
groups = await this.getGroups(groupForce, !test, setFilter);
if (setFilter != null && users != null) {
users = users.filter((u) => {
if (u.disabled || u.deleted) {
@@ -72,7 +72,7 @@ export class AzureDirectoryService implements DirectoryService {
return [groups, users];
}
private async getUsers(force: boolean): Promise<UserEntry[]> {
private async getUsers(force: boolean, saveDelta: boolean): Promise<UserEntry[]> {
const entries: UserEntry[] = [];
let res: any = null;
@@ -95,7 +95,7 @@ export class AzureDirectoryService implements DirectoryService {
while (true) {
const users: graphType.User[] = res.value;
if (users != null) {
users.forEach((user) => {
for (const user of users) {
const entry = this.buildUser(user);
if (this.filterOutResult(filter, entry.email)) {
return;
@@ -107,11 +107,11 @@ export class AzureDirectoryService implements DirectoryService {
}
entries.push(entry);
});
}
}
if (res[NextLink] == null) {
if (res[DeltaLink] != null) {
if (res[DeltaLink] != null && saveDelta) {
await this.configurationService.saveUserDeltaToken(res[DeltaLink]);
}
break;
@@ -138,7 +138,8 @@ export class AzureDirectoryService implements DirectoryService {
return entry;
}
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
private async getGroups(force: boolean, saveDelta: boolean,
setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
const entries: GroupEntry[] = [];
const changedGroupIds: string[] = [];
const token = await this.configurationService.getGroupDeltaToken();
@@ -179,7 +180,7 @@ export class AzureDirectoryService implements DirectoryService {
}
if (res[NextLink] == null) {
if (res[DeltaLink] != null) {
if (res[DeltaLink] != null && saveDelta) {
await this.configurationService.saveGroupDeltaToken(res[DeltaLink]);
}
break;
@@ -232,13 +233,13 @@ export class AzureDirectoryService implements DirectoryService {
const memRes = await memReq.get();
const members: any = memRes.value;
if (members != null) {
members.forEach((member: any) => {
for (const member of members) {
if (member[ObjectType] === '#microsoft.graph.group') {
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
} else if (member[ObjectType] === '#microsoft.graph.user') {
entry.userMemberExternalIds.add((member as graphType.User).id);
}
});
}
}
return entry;
@@ -266,9 +267,9 @@ export class AzureDirectoryService implements DirectoryService {
const set = new Set<string>();
const pieces = parts[1].split(',');
pieces.forEach((p) => {
for (const p of pieces) {
set.add(p.trim().toLowerCase());
});
}
return [exclude, set];
}

View File

@@ -18,6 +18,8 @@ const Keys = {
groupDelta: 'groupDeltaToken',
lastUserSync: 'lastUserSync',
lastGroupSync: 'lastGroupSync',
lastSyncHash: 'lastSyncHash',
organizationId: 'organizationId',
};
export class ConfigurationService {
@@ -145,4 +147,28 @@ export class ConfigurationService {
return this.storageService.save(Keys.lastGroupSync, date);
}
}
getLastSyncHash(): Promise<string> {
return this.storageService.get<string>(Keys.lastSyncHash);
}
saveLastSyncHash(hash: string) {
if (hash == null) {
return this.storageService.remove(Keys.lastSyncHash);
} else {
return this.storageService.save(Keys.lastSyncHash, hash);
}
}
getOrganizationId(): Promise<string> {
return this.storageService.get<string>(Keys.organizationId);
}
saveOrganizationId(id: string) {
if (id == null) {
return this.storageService.remove(Keys.organizationId);
} else {
return this.storageService.save(Keys.organizationId, id);
}
}
}

View File

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

View File

@@ -32,7 +32,7 @@ export class GSuiteDirectoryService implements DirectoryService {
this.service = google.admin<Admin>('directory_v1');
}
async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> {
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.GSuite) {
return;
@@ -76,7 +76,7 @@ export class GSuiteDirectoryService implements DirectoryService {
const filter = this.createSet(this.syncConfig.userFilter);
if (res.data.users != null) {
res.data.users.forEach((user) => {
for (const user of res.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
return;
}
@@ -85,7 +85,7 @@ export class GSuiteDirectoryService implements DirectoryService {
if (entry != null) {
entries.push(entry);
}
});
}
}
this.logService.info('Querying deleted users.');
@@ -96,7 +96,7 @@ export class GSuiteDirectoryService implements DirectoryService {
}
if (delRes.data.users != null) {
delRes.data.users.forEach((user) => {
for (const user of delRes.data.users) {
if (this.filterOutResult(filter, user.primaryEmail)) {
return;
}
@@ -105,7 +105,7 @@ export class GSuiteDirectoryService implements DirectoryService {
if (entry != null) {
entries.push(entry);
}
});
}
}
return entries;
@@ -164,7 +164,7 @@ export class GSuiteDirectoryService implements DirectoryService {
}
if (memRes.data.members != null) {
memRes.data.members.forEach((member) => {
for (const member of memRes.data.members) {
if (member.role.toLowerCase() !== 'member') {
return;
}
@@ -177,7 +177,7 @@ export class GSuiteDirectoryService implements DirectoryService {
} else if (member.type.toLowerCase() === 'group') {
entry.groupMemberReferenceIds.add(member.id);
}
});
}
}
return entry;
@@ -223,9 +223,9 @@ export class GSuiteDirectoryService implements DirectoryService {
const set = new Set<string>();
const pieces = parts[1].split(',');
pieces.forEach((p) => {
for (const p of pieces) {
set.add(p.trim().toLowerCase());
});
}
return [exclude, set];
}

View File

@@ -22,7 +22,7 @@ export class LdapDirectoryService implements DirectoryService {
constructor(private configurationService: ConfigurationService, private logService: LogService) { }
async getEntries(force = false): Promise<[GroupEntry[], UserEntry[]]> {
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
const type = await this.configurationService.getDirectoryType();
if (type !== DirectoryType.Ldap) {
return;
@@ -61,7 +61,6 @@ export class LdapDirectoryService implements DirectoryService {
private async getUsers(force: boolean): Promise<UserEntry[]> {
const lastSync = await this.configurationService.getLastUserSyncDate();
let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter);
filter = this.buildRevisionFilter(filter, force, lastSync);
@@ -121,7 +120,6 @@ export class LdapDirectoryService implements DirectoryService {
const entries: GroupEntry[] = [];
const lastSync = await this.configurationService.getLastUserSyncDate();
const originalFilter = this.buildBaseFilter(this.syncConfig.groupObjectClass, this.syncConfig.groupFilter);
let filter = originalFilter;
const revisionFilter = this.buildRevisionFilter(filter, force, lastSync);
@@ -151,12 +149,12 @@ export class LdapDirectoryService implements DirectoryService {
return se;
});
groupSearchEntries.forEach((se) => {
for (const se of groupSearchEntries) {
const group = this.buildGroup(se, userIdMap);
if (group != null) {
entries.push(group);
}
});
}
return entries;
}
@@ -181,13 +179,13 @@ export class LdapDirectoryService implements DirectoryService {
const members = this.getAttrVals(searchEntry, this.syncConfig.memberAttribute);
if (members != null) {
members.forEach((memDn) => {
for (const memDn of members) {
if (userMap.has(memDn) && !group.userMemberExternalIds.has(userMap.get(memDn))) {
group.userMemberExternalIds.add(userMap.get(memDn));
} else if (!group.groupMemberReferenceIds.has(memDn)) {
group.groupMemberReferenceIds.add(memDn);
}
});
}
}
return group;
@@ -326,9 +324,14 @@ export class LdapDirectoryService implements DirectoryService {
const pass = this.dirConfig.password == null || this.dirConfig.password.trim() === '' ? null :
this.dirConfig.password;
if (user == null || pass == null) {
reject('Username and/or password re not configured.');
return;
}
this.client.bind(user, pass, (err) => {
if (err != null) {
reject(err);
reject('Error authenticating: ' + err.message);
} else {
resolve();
}

View File

@@ -7,6 +7,8 @@ import { ImportDirectoryRequest } from 'jslib/models/request/importDirectoryRequ
import { ImportDirectoryRequestGroup } from 'jslib/models/request/importDirectoryRequestGroup';
import { ImportDirectoryRequestUser } from 'jslib/models/request/importDirectoryRequestUser';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { LogService } from 'jslib/abstractions/log.service';
import { StorageService } from 'jslib/abstractions/storage.service';
@@ -15,6 +17,7 @@ import { ConfigurationService } from './configuration.service';
import { DirectoryService } from './directory.service';
import { GSuiteDirectoryService } from './gsuite-directory.service';
import { LdapDirectoryService } from './ldap-directory.service';
import { Utils } from 'jslib/misc/utils';
const Keys = {
};
@@ -22,9 +25,10 @@ const Keys = {
export class SyncService {
private dirType: DirectoryType;
constructor(private configurationService: ConfigurationService, private logService: LogService) { }
constructor(private configurationService: ConfigurationService, private logService: LogService,
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService) { }
async sync(force = true, sendToServer = true): Promise<[GroupEntry[], UserEntry[]]> {
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
this.dirType = await this.configurationService.getDirectoryType();
if (this.dirType == null) {
throw new Error('No directory configured.');
@@ -41,7 +45,7 @@ export class SyncService {
const now = new Date();
try {
const entries = await directoryService.getEntries(force);
const entries = await directoryService.getEntries(force, test);
const groups = entries[0];
const users = entries[1];
@@ -49,27 +53,48 @@ export class SyncService {
this.flattenUsersToGroups(groups, null, groups);
}
console.log(groups);
console.log(users);
if (!sendToServer) {
// TODO: restore deltas
}
if (!sendToServer || groups == null || groups.length === 0 || users == null || users.length === 0) {
if (test || groups == null || groups.length === 0 || users == null || users.length === 0) {
return [groups, users];
}
const req = this.buildRequest(groups, users, syncConfig.removeDisabled);
const reqJson = JSON.stringify(req);
let hash: string = null;
const hashBuf = await this.cryptoFunctionService.hash(this.apiService.baseUrl + reqJson, 'sha256');
if (hashBuf != null) {
hash = Utils.fromBufferToB64(hashBuf);
}
const lastHash = await this.configurationService.getLastSyncHash();
if (lastHash == null || hash !== lastHash) {
const orgId = await this.configurationService.getOrganizationId();
if (orgId == null) {
throw new Error('Organization not set.');
}
const res = await this.apiService.postImportDirectory(orgId, req);
await this.configurationService.saveLastSyncHash(hash);
if (syncConfig.groups) {
await this.configurationService.saveLastGroupSyncDate(now);
}
if (syncConfig.users) {
await this.configurationService.saveLastUserSyncDate(now);
}
}
return [groups, users];
} catch (e) {
// TODO: restore deltas
// failed sync result
if (!test) {
await this.configurationService.saveGroupDeltaToken(startingGroupDelta);
await this.configurationService.saveUserDeltaToken(startingUserDelta);
}
throw e;
}
}
private flattenUsersToGroups(currentGroups: GroupEntry[], currentGroupsUsers: string[], allGroups: GroupEntry[]) {
currentGroups.forEach((group) => {
for (const group of currentGroups) {
const groupsInThisGroup = allGroups.filter((g) => group.groupMemberReferenceIds.has(g.referenceId));
let usersInThisGroup = Array.from(group.userMemberExternalIds);
@@ -79,7 +104,7 @@ export class SyncService {
}
this.flattenUsersToGroups(groupsInThisGroup, usersInThisGroup, allGroups);
});
}
}
private getDirectoryService(): DirectoryService {
@@ -99,23 +124,23 @@ export class SyncService {
const model = new ImportDirectoryRequest();
if (groups != null) {
groups.forEach((g) => {
for (const g of groups) {
const ig = new ImportDirectoryRequestGroup();
ig.name = g.name;
ig.externalId = g.externalId;
ig.users = Array.from(g.userMemberExternalIds);
model.groups.push(ig);
});
}
}
if (users != null) {
users.forEach((u) => {
for (const u of users) {
const iu = new ImportDirectoryRequestUser();
iu.email = u.email;
iu.externalId = u.externalId;
iu.deleted = u.deleted || (removeDisabled && u.disabled);
model.users.push(iu);
});
}
}
return model;