mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
Pm-10953/add-user-context-to-sync-replaces (#10627)
* Require userId for setting masterKeyEncryptedUserKey * Replace folders for specified user * Require userId for collection replace * Cipher Replace requires userId * Require UserId to update equivalent domains * Require userId for policy replace * sync state updates between fake state for better testing * Revert to public observable tests Since they now sync, we can test single-user updates impacting active user observables * Do not init fake states through sync Do not sync initial null values, that might wipe out already existing data. * Require userId for Send replace * Include userId for organization replace * Require userId for billing sync data * Require user Id for key connector sync data * Allow decode of token by userId * Require userId for synced key connector updates * Add userId to policy setting during organization invite accept * Fix cli * Handle null userId --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
This commit is contained in:
@@ -143,7 +143,7 @@ export abstract class CryptoService {
|
||||
* @param userKeyMasterKey The master key encrypted user key to set
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId?: string): Promise<void>;
|
||||
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId: string): Promise<void>;
|
||||
/**
|
||||
* @param password The user's master password that will be used to derive a master key if one isn't found
|
||||
* @param userId The desired user
|
||||
|
||||
@@ -119,7 +119,7 @@ describe("BiometricStateService", () => {
|
||||
|
||||
describe("getRequirePasswordOnStart", () => {
|
||||
it("returns the requirePasswordOnStart state value", async () => {
|
||||
stateProvider.singleUser.mockFor(userId, REQUIRE_PASSWORD_ON_START.key, true);
|
||||
stateProvider.singleUser.mockFor(userId, REQUIRE_PASSWORD_ON_START, true);
|
||||
|
||||
expect(await sut.getRequirePasswordOnStart(userId)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -365,9 +365,9 @@ describe("cryptoService", () => {
|
||||
const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
|
||||
const fakeMasterKey = makeMasterKey ? makeSymmetricCryptoKey<MasterKey>(64) : null;
|
||||
masterPasswordService.masterKeySubject.next(fakeMasterKey);
|
||||
userKeyState.stateSubject.next([mockUserId, null]);
|
||||
userKeyState.nextState(null);
|
||||
const fakeUserKey = makeUserKey ? makeSymmetricCryptoKey<UserKey>(64) : null;
|
||||
userKeyState.stateSubject.next([mockUserId, fakeUserKey]);
|
||||
userKeyState.nextState(fakeUserKey);
|
||||
return [fakeUserKey, fakeMasterKey];
|
||||
}
|
||||
|
||||
@@ -384,10 +384,7 @@ describe("cryptoService", () => {
|
||||
|
||||
const fakeEncryptedUserPrivateKey = makeEncString("1");
|
||||
|
||||
userEncryptedPrivateKeyState.stateSubject.next([
|
||||
mockUserId,
|
||||
fakeEncryptedUserPrivateKey.encryptedString,
|
||||
]);
|
||||
userEncryptedPrivateKeyState.nextState(fakeEncryptedUserPrivateKey.encryptedString);
|
||||
|
||||
// Decryption of the user private key
|
||||
const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1);
|
||||
@@ -423,7 +420,7 @@ describe("cryptoService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_PRIVATE_KEY,
|
||||
);
|
||||
encryptedUserPrivateKeyState.stateSubject.next([mockUserId, null]);
|
||||
encryptedUserPrivateKeyState.nextState(null);
|
||||
|
||||
const userPrivateKey = await firstValueFrom(cryptoService.userPrivateKey$(mockUserId));
|
||||
expect(userPrivateKey).toBeFalsy();
|
||||
@@ -463,7 +460,7 @@ describe("cryptoService", () => {
|
||||
function updateKeys(keys: Partial<UpdateKeysParams> = {}) {
|
||||
if ("userKey" in keys) {
|
||||
const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
|
||||
userKeyState.stateSubject.next([mockUserId, keys.userKey]);
|
||||
userKeyState.nextState(keys.userKey);
|
||||
}
|
||||
|
||||
if ("encryptedPrivateKey" in keys) {
|
||||
@@ -471,10 +468,7 @@ describe("cryptoService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_PRIVATE_KEY,
|
||||
);
|
||||
userEncryptedPrivateKey.stateSubject.next([
|
||||
mockUserId,
|
||||
keys.encryptedPrivateKey.encryptedString,
|
||||
]);
|
||||
userEncryptedPrivateKey.nextState(keys.encryptedPrivateKey.encryptedString);
|
||||
}
|
||||
|
||||
if ("orgKeys" in keys) {
|
||||
@@ -482,7 +476,7 @@ describe("cryptoService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_ORGANIZATION_KEYS,
|
||||
);
|
||||
orgKeysState.stateSubject.next([mockUserId, keys.orgKeys]);
|
||||
orgKeysState.nextState(keys.orgKeys);
|
||||
}
|
||||
|
||||
if ("providerKeys" in keys) {
|
||||
@@ -490,7 +484,7 @@ describe("cryptoService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_PROVIDER_KEYS,
|
||||
);
|
||||
providerKeysState.stateSubject.next([mockUserId, keys.providerKeys]);
|
||||
providerKeysState.nextState(keys.providerKeys);
|
||||
}
|
||||
|
||||
encryptService.decryptToBytes.mockImplementation((encryptedPrivateKey, userKey) => {
|
||||
|
||||
@@ -225,7 +225,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId: UserId): Promise<void> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
|
||||
new EncString(userKeyMasterKey),
|
||||
|
||||
@@ -143,7 +143,7 @@ describe("DefaultStateProvider", () => {
|
||||
it("should not emit any values until a truthy user id is supplied", async () => {
|
||||
accountService.activeAccountSubject.next(null);
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
state.stateSubject.next([userId, "value"]);
|
||||
state.nextState("value");
|
||||
|
||||
const emissions = trackEmissions(sut.getUserState$(keyDefinition));
|
||||
|
||||
|
||||
@@ -124,12 +124,12 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
const response = await this.apiService.getSync();
|
||||
|
||||
await this.syncProfile(response.profile);
|
||||
await this.syncFolders(response.folders);
|
||||
await this.syncCollections(response.collections);
|
||||
await this.syncCiphers(response.ciphers);
|
||||
await this.syncSends(response.sends);
|
||||
await this.syncSettings(response.domains);
|
||||
await this.syncPolicies(response.policies);
|
||||
await this.syncFolders(response.folders, response.profile.id);
|
||||
await this.syncCollections(response.collections, response.profile.id);
|
||||
await this.syncCiphers(response.ciphers, response.profile.id);
|
||||
await this.syncSends(response.sends, response.profile.id);
|
||||
await this.syncSettings(response.domains, response.profile.id);
|
||||
await this.syncPolicies(response.policies, response.profile.id);
|
||||
|
||||
await this.setLastSync(now, userId);
|
||||
return this.syncCompleted(true);
|
||||
@@ -190,8 +190,9 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
await this.billingAccountProfileStateService.setHasPremium(
|
||||
response.premiumPersonally,
|
||||
response.premiumFromOrganization,
|
||||
response.id,
|
||||
);
|
||||
await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector);
|
||||
await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector, response.id);
|
||||
|
||||
await this.setForceSetPasswordReasonIfNeeded(response);
|
||||
|
||||
@@ -200,17 +201,17 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
providers[p.id] = new ProviderData(p);
|
||||
});
|
||||
|
||||
await this.providerService.save(providers);
|
||||
await this.providerService.save(providers, response.id);
|
||||
|
||||
await this.syncProfileOrganizations(response);
|
||||
await this.syncProfileOrganizations(response, response.id);
|
||||
|
||||
if (await this.keyConnectorService.userNeedsMigration()) {
|
||||
await this.keyConnectorService.setConvertAccountRequired(true);
|
||||
if (await this.keyConnectorService.userNeedsMigration(response.id)) {
|
||||
await this.keyConnectorService.setConvertAccountRequired(true, response.id);
|
||||
this.messageSender.send("convertAccountToKeyConnector");
|
||||
} else {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.keyConnectorService.removeConvertAccountRequired();
|
||||
this.keyConnectorService.removeConvertAccountRequired(response.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +262,7 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private async syncProfileOrganizations(response: ProfileResponse) {
|
||||
private async syncProfileOrganizations(response: ProfileResponse, userId: UserId) {
|
||||
const organizations: { [id: string]: OrganizationData } = {};
|
||||
response.organizations.forEach((o) => {
|
||||
organizations[o.id] = new OrganizationData(o, {
|
||||
@@ -281,42 +282,42 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
}
|
||||
});
|
||||
|
||||
await this.organizationService.replace(organizations);
|
||||
await this.organizationService.replace(organizations, userId);
|
||||
}
|
||||
|
||||
private async syncFolders(response: FolderResponse[]) {
|
||||
private async syncFolders(response: FolderResponse[], userId: UserId) {
|
||||
const folders: { [id: string]: FolderData } = {};
|
||||
response.forEach((f) => {
|
||||
folders[f.id] = new FolderData(f);
|
||||
});
|
||||
return await this.folderService.replace(folders);
|
||||
return await this.folderService.replace(folders, userId);
|
||||
}
|
||||
|
||||
private async syncCollections(response: CollectionDetailsResponse[]) {
|
||||
private async syncCollections(response: CollectionDetailsResponse[], userId: UserId) {
|
||||
const collections: { [id: string]: CollectionData } = {};
|
||||
response.forEach((c) => {
|
||||
collections[c.id] = new CollectionData(c);
|
||||
});
|
||||
return await this.collectionService.replace(collections);
|
||||
return await this.collectionService.replace(collections, userId);
|
||||
}
|
||||
|
||||
private async syncCiphers(response: CipherResponse[]) {
|
||||
private async syncCiphers(response: CipherResponse[], userId: UserId) {
|
||||
const ciphers: { [id: string]: CipherData } = {};
|
||||
response.forEach((c) => {
|
||||
ciphers[c.id] = new CipherData(c);
|
||||
});
|
||||
return await this.cipherService.replace(ciphers);
|
||||
return await this.cipherService.replace(ciphers, userId);
|
||||
}
|
||||
|
||||
private async syncSends(response: SendResponse[]) {
|
||||
private async syncSends(response: SendResponse[], userId: UserId) {
|
||||
const sends: { [id: string]: SendData } = {};
|
||||
response.forEach((s) => {
|
||||
sends[s.id] = new SendData(s);
|
||||
});
|
||||
return await this.sendService.replace(sends);
|
||||
return await this.sendService.replace(sends, userId);
|
||||
}
|
||||
|
||||
private async syncSettings(response: DomainsResponse) {
|
||||
private async syncSettings(response: DomainsResponse, userId: UserId) {
|
||||
let eqDomains: string[][] = [];
|
||||
if (response != null && response.equivalentDomains != null) {
|
||||
eqDomains = eqDomains.concat(response.equivalentDomains);
|
||||
@@ -330,16 +331,16 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
});
|
||||
}
|
||||
|
||||
return this.domainSettingsService.setEquivalentDomains(eqDomains);
|
||||
return this.domainSettingsService.setEquivalentDomains(eqDomains, userId);
|
||||
}
|
||||
|
||||
private async syncPolicies(response: PolicyResponse[]) {
|
||||
private async syncPolicies(response: PolicyResponse[], userId: UserId) {
|
||||
const policies: { [id: string]: PolicyData } = {};
|
||||
if (response != null) {
|
||||
response.forEach((p) => {
|
||||
policies[p.id] = new PolicyData(p);
|
||||
});
|
||||
}
|
||||
return await this.policyService.replace(policies);
|
||||
return await this.policyService.replace(policies, userId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user