1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-27 21:53:25 +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:
Matt Gibson
2024-08-26 17:44:08 -07:00
committed by GitHub
parent 866a624e44
commit 9459cda304
46 changed files with 666 additions and 484 deletions

View File

@@ -32,7 +32,7 @@ export class FakeGlobalStateProvider implements GlobalStateProvider {
states: Map<string, GlobalState<unknown>> = new Map();
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
this.mock.get(keyDefinition);
const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`;
const cacheKey = this.cacheKey(keyDefinition);
let result = this.states.get(cacheKey);
if (result == null) {
@@ -53,94 +53,143 @@ export class FakeGlobalStateProvider implements GlobalStateProvider {
return result as GlobalState<T>;
}
private cacheKey(keyDefinition: KeyDefinition<unknown>) {
return `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`;
}
getFake<T>(keyDefinition: KeyDefinition<T>): FakeGlobalState<T> {
return this.get(keyDefinition) as FakeGlobalState<T>;
}
mockFor<T>(keyDefinitionKey: string, initialValue?: T): FakeGlobalState<T> {
if (!this.establishedMocks.has(keyDefinitionKey)) {
this.establishedMocks.set(keyDefinitionKey, new FakeGlobalState<T>(initialValue));
mockFor<T>(keyDefinition: KeyDefinition<T>, initialValue?: T): FakeGlobalState<T> {
const cacheKey = this.cacheKey(keyDefinition);
if (!this.states.has(cacheKey)) {
this.states.set(cacheKey, new FakeGlobalState<T>(initialValue));
}
return this.establishedMocks.get(keyDefinitionKey) as FakeGlobalState<T>;
return this.states.get(cacheKey) as FakeGlobalState<T>;
}
}
export class FakeSingleUserStateProvider implements SingleUserStateProvider {
mock = mock<SingleUserStateProvider>();
establishedMocks: Map<string, FakeSingleUserState<unknown>> = new Map();
states: Map<string, SingleUserState<unknown>> = new Map();
constructor(
readonly updateSyncCallback?: (
key: UserKeyDefinition<unknown>,
userId: UserId,
newValue: unknown,
) => Promise<void>,
) {}
get<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
this.mock.get(userId, userKeyDefinition);
const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}_${userId}`;
const cacheKey = this.cacheKey(userId, userKeyDefinition);
let result = this.states.get(cacheKey);
if (result == null) {
let fake: FakeSingleUserState<T>;
// Look for established mock
if (this.establishedMocks.has(userKeyDefinition.key)) {
fake = this.establishedMocks.get(userKeyDefinition.key) as FakeSingleUserState<T>;
} else {
fake = new FakeSingleUserState<T>(userId);
}
fake.keyDefinition = userKeyDefinition;
result = fake;
result = this.buildFakeState(userId, userKeyDefinition);
this.states.set(cacheKey, result);
}
return result as SingleUserState<T>;
}
getFake<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): FakeSingleUserState<T> {
getFake<T>(
userId: UserId,
userKeyDefinition: UserKeyDefinition<T>,
{ allowInit }: { allowInit: boolean } = { allowInit: true },
): FakeSingleUserState<T> {
if (!allowInit && this.states.get(this.cacheKey(userId, userKeyDefinition)) == null) {
return null;
}
return this.get(userId, userKeyDefinition) as FakeSingleUserState<T>;
}
mockFor<T>(userId: UserId, keyDefinitionKey: string, initialValue?: T): FakeSingleUserState<T> {
if (!this.establishedMocks.has(keyDefinitionKey)) {
this.establishedMocks.set(keyDefinitionKey, new FakeSingleUserState<T>(userId, initialValue));
mockFor<T>(
userId: UserId,
userKeyDefinition: UserKeyDefinition<T>,
initialValue?: T,
): FakeSingleUserState<T> {
const cacheKey = this.cacheKey(userId, userKeyDefinition);
if (!this.states.has(cacheKey)) {
this.states.set(cacheKey, this.buildFakeState(userId, userKeyDefinition, initialValue));
}
return this.establishedMocks.get(keyDefinitionKey) as FakeSingleUserState<T>;
return this.states.get(cacheKey) as FakeSingleUserState<T>;
}
private buildFakeState<T>(
userId: UserId,
userKeyDefinition: UserKeyDefinition<T>,
initialValue?: T,
) {
const state = new FakeSingleUserState(userId, initialValue, async (...args) => {
await this.updateSyncCallback?.(userKeyDefinition, ...args);
});
state.keyDefinition = userKeyDefinition;
return state;
}
private cacheKey(userId: UserId, userKeyDefinition: UserKeyDefinition<unknown>) {
return `${userKeyDefinitionCacheKey(userKeyDefinition)}_${userId}`;
}
}
export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
activeUserId$: Observable<UserId>;
establishedMocks: Map<string, FakeActiveUserState<unknown>> = new Map();
states: Map<string, FakeActiveUserState<unknown>> = new Map();
constructor(public accountService: FakeAccountService) {
constructor(
public accountService: FakeAccountService,
readonly updateSyncCallback?: (
key: UserKeyDefinition<unknown>,
userId: UserId,
newValue: unknown,
) => Promise<void>,
) {
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id));
}
get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}`;
const cacheKey = userKeyDefinitionCacheKey(userKeyDefinition);
let result = this.states.get(cacheKey);
if (result == null) {
// Look for established mock
if (this.establishedMocks.has(userKeyDefinition.key)) {
result = this.establishedMocks.get(userKeyDefinition.key);
} else {
result = new FakeActiveUserState<T>(this.accountService);
}
result.keyDefinition = userKeyDefinition;
result = this.buildFakeState(userKeyDefinition);
this.states.set(cacheKey, result);
}
return result as ActiveUserState<T>;
}
getFake<T>(userKeyDefinition: UserKeyDefinition<T>): FakeActiveUserState<T> {
getFake<T>(
userKeyDefinition: UserKeyDefinition<T>,
{ allowInit }: { allowInit: boolean } = { allowInit: true },
): FakeActiveUserState<T> {
if (!allowInit && this.states.get(userKeyDefinitionCacheKey(userKeyDefinition)) == null) {
return null;
}
return this.get(userKeyDefinition) as FakeActiveUserState<T>;
}
mockFor<T>(keyDefinitionKey: string, initialValue?: T): FakeActiveUserState<T> {
if (!this.establishedMocks.has(keyDefinitionKey)) {
this.establishedMocks.set(
keyDefinitionKey,
new FakeActiveUserState<T>(this.accountService, initialValue),
);
mockFor<T>(userKeyDefinition: UserKeyDefinition<T>, initialValue?: T): FakeActiveUserState<T> {
const cacheKey = userKeyDefinitionCacheKey(userKeyDefinition);
if (!this.states.has(cacheKey)) {
this.states.set(cacheKey, this.buildFakeState(userKeyDefinition, initialValue));
}
return this.establishedMocks.get(keyDefinitionKey) as FakeActiveUserState<T>;
return this.states.get(cacheKey) as FakeActiveUserState<T>;
}
private buildFakeState<T>(userKeyDefinition: UserKeyDefinition<T>, initialValue?: T) {
const state = new FakeActiveUserState<T>(this.accountService, initialValue, async (...args) => {
await this.updateSyncCallback?.(userKeyDefinition, ...args);
});
state.keyDefinition = userKeyDefinition;
return state;
}
}
function userKeyDefinitionCacheKey(userKeyDefinition: UserKeyDefinition<unknown>) {
return `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}`;
}
export class FakeStateProvider implements StateProvider {
@@ -207,9 +256,35 @@ export class FakeStateProvider implements StateProvider {
constructor(public accountService: FakeAccountService) {}
private distributeSingleUserUpdate(
key: UserKeyDefinition<unknown>,
userId: UserId,
newState: unknown,
) {
if (this.activeUser.accountService.activeUserId === userId) {
const state = this.activeUser.getFake(key, { allowInit: false });
state?.nextState(newState, { syncValue: false });
}
}
private distributeActiveUserUpdate(
key: UserKeyDefinition<unknown>,
userId: UserId,
newState: unknown,
) {
this.singleUser
.getFake(userId, key, { allowInit: false })
?.nextState(newState, { syncValue: false });
}
global: FakeGlobalStateProvider = new FakeGlobalStateProvider();
singleUser: FakeSingleUserStateProvider = new FakeSingleUserStateProvider();
activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider(this.accountService);
singleUser: FakeSingleUserStateProvider = new FakeSingleUserStateProvider(
this.distributeSingleUserUpdate.bind(this),
);
activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider(
this.accountService,
this.distributeActiveUserUpdate.bind(this),
);
derived: FakeDerivedStateProvider = new FakeDerivedStateProvider();
activeUserId$: Observable<UserId> = this.activeUser.activeUserId$;
}