1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-22 07:50:04 +02:00

integrate forwarder storage into credential service; breaks tests

This commit is contained in:
✨ Audrey ✨ 2024-10-15 16:48:13 -04:00
parent 1ad1b900ea
commit 51d29836e5
No known key found for this signature in database
GPG Key ID: 0CF8B4C0D9088B97
10 changed files with 263 additions and 25 deletions

View File

@ -19,7 +19,7 @@ export type ObjectKey<State, Secret = State, Disclosed = Record<string, never>>
target: "object";
key: string;
state: StateDefinition;
classifier: Classifier<void, Disclosed, Secret>;
classifier: Classifier<State, Disclosed, Secret>;
format: "plain" | "classified";
options: UserKeyDefinitionOptions<State>;
};

View File

@ -4,6 +4,7 @@ import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
import { IntegrationRequest } from "@bitwarden/common/tools/integration/rpc/integration-request";
import { RpcConfiguration } from "@bitwarden/common/tools/integration/rpc/rpc-definition";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderContext } from "./forwarder-context";
@ -34,14 +35,25 @@ export type ForwarderConfiguration<
/** default value of all fields */
defaultSettings: Partial<Settings>;
/** forwarder settings storage */
// FIXME: this should be a `SubjectConfiguration<Settings>`
/** forwarder settings storage
* @deprecated use local.settings instead
*/
settings: UserKeyDefinition<Settings>;
/** forwarder settings import buffer; `undefined` when there is no buffer. */
// FIXME: this should be a `SubjectConfiguration<Settings>`
/** forwarder settings import buffer; `undefined` when there is no buffer.
* @deprecated use local.settings import
*/
importBuffer?: BufferedKeyDefinition<Settings>;
/** locally stored data; forwarder-partitioned */
local: {
/** integration settings storage */
settings: ObjectKey<Settings>;
/** plaintext import buffer - used during data migrations */
import?: ObjectKey<Settings, Record<string, never>, Settings>;
};
/** createForwardingEmail RPC definition */
createForwardingEmail: CreateForwardingEmailRpcDef<Settings, Request>;

View File

@ -1,11 +1,18 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
IntegrationRequest,
SelfHostedApiSettings,
} from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderConfiguration, ForwarderContext, EmailDomainSettings } from "../engine";
import { CreateForwardingEmailRpcDef } from "../engine/forwarder-configuration";
@ -44,6 +51,33 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
createForwardingEmail,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.AddyIo.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<AddyIoSettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<AddyIoSettings>,
import: {
key: "forwarder.AddyIo.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<AddyIoSettings>(["token", "baseUrl", "domain"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<AddyIoSettings, Record<string, never>, AddyIoSettings>,
},
settings: new UserKeyDefinition<AddyIoSettings>(GENERATOR_DISK, "addyIoForwarder", {
deserializer: (value) => value,
clearOn: [],
@ -52,7 +86,6 @@ const forwarder = Object.freeze({
deserializer: (value) => value,
clearOn: ["logout"],
}),
createForwardingEmail,
} as const);
export const AddyIo = Object.freeze({

View File

@ -1,7 +1,14 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderConfiguration, ForwarderContext } from "../engine";
import { CreateForwardingEmailRpcDef } from "../engine/forwarder-configuration";
@ -36,6 +43,33 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
createForwardingEmail,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.DuckDuckGo.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<DuckDuckGoSettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<DuckDuckGoSettings>,
import: {
key: "forwarder.DuckDuckGo.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<DuckDuckGoSettings>(["token"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<DuckDuckGoSettings, Record<string, never>, DuckDuckGoSettings>,
},
settings: new UserKeyDefinition<DuckDuckGoSettings>(GENERATOR_DISK, "duckDuckGoForwarder", {
deserializer: (value) => value,
clearOn: [],
@ -44,7 +78,6 @@ const forwarder = Object.freeze({
deserializer: (value) => value,
clearOn: ["logout"],
}),
createForwardingEmail,
} as const);
// integration-wide configuration

View File

@ -1,7 +1,14 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import {
ForwarderConfiguration,
@ -101,6 +108,34 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
createForwardingEmail,
getAccountId,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.Fastmail.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<FastmailSettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<FastmailSettings>,
import: {
key: "forwarder.Fastmail.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<FastmailSettings>(["token", "domain", "prefix"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<FastmailSettings, Record<string, never>, FastmailSettings>,
},
settings: new UserKeyDefinition<FastmailSettings>(GENERATOR_DISK, "fastmailForwarder", {
deserializer: (value) => value,
clearOn: [],
@ -109,8 +144,6 @@ const forwarder = Object.freeze({
deserializer: (value) => value,
clearOn: ["logout"],
}),
createForwardingEmail,
getAccountId,
} as const);
// integration-wide configuration

View File

@ -1,7 +1,14 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderConfiguration, ForwarderContext } from "../engine";
import { CreateForwardingEmailRpcDef } from "../engine/forwarder-configuration";
@ -40,6 +47,33 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
createForwardingEmail,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.Firefox.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<FirefoxRelaySettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<FirefoxRelaySettings>,
import: {
key: "forwarder.Firefox.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<FirefoxRelaySettings>(["token"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<FirefoxRelaySettings, Record<string, never>, FirefoxRelaySettings>,
},
settings: new UserKeyDefinition<FirefoxRelaySettings>(GENERATOR_DISK, "firefoxRelayForwarder", {
deserializer: (value) => value,
clearOn: [],
@ -52,7 +86,6 @@ const forwarder = Object.freeze({
clearOn: ["logout"],
},
),
createForwardingEmail,
} as const);
// integration-wide configuration

View File

@ -1,7 +1,14 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderConfiguration, ForwarderContext, EmailDomainSettings } from "../engine";
import { CreateForwardingEmailRpcDef } from "../engine/forwarder-configuration";
@ -43,6 +50,32 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.ForwardEmail.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<ForwardEmailSettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<ForwardEmailSettings>,
import: {
key: "forwarder.ForwardEmail.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<ForwardEmailSettings>(["token", "domain"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<ForwardEmailSettings, Record<string, never>, ForwardEmailSettings>,
},
settings: new UserKeyDefinition<ForwardEmailSettings>(GENERATOR_DISK, "forwardEmailForwarder", {
deserializer: (value) => value,
clearOn: [],

View File

@ -1,11 +1,18 @@
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
import {
GENERATOR_DISK,
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
IntegrationRequest,
SelfHostedApiSettings,
} from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { BufferedKeyDefinition } from "@bitwarden/common/tools/state/buffered-key-definition";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { ForwarderConfiguration, ForwarderContext } from "../engine";
import { CreateForwardingEmailRpcDef } from "../engine/forwarder-configuration";
@ -45,6 +52,33 @@ const createForwardingEmail = Object.freeze({
// forwarder configuration
const forwarder = Object.freeze({
defaultSettings,
createForwardingEmail,
local: {
settings: {
// FIXME: integration should issue keys at runtime
// based on integrationId & extension metadata
key: "forwarder.SimpleLogin.local.settings",
target: "object",
format: "classified",
classifier: new PrivateClassifier<SimpleLoginSettings>(),
state: GENERATOR_DISK,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ObjectKey<SimpleLoginSettings>,
import: {
key: "forwarder.SimpleLogin.local.import",
target: "object",
format: "plain",
classifier: new PublicClassifier<SimpleLoginSettings>(["token", "baseUrl"]),
state: GENERATOR_MEMORY,
options: {
deserializer: (value) => value,
clearOn: ["logout", "lock"],
},
} satisfies ObjectKey<SimpleLoginSettings, Record<string, never>, SimpleLoginSettings>,
},
settings: new UserKeyDefinition<SimpleLoginSettings>(GENERATOR_DISK, "simpleLoginForwarder", {
deserializer: (value) => value,
clearOn: [],
@ -57,7 +91,6 @@ const forwarder = Object.freeze({
clearOn: ["logout"],
},
),
createForwardingEmail,
} as const);
// integration-wide configuration

View File

@ -16,6 +16,7 @@ import {
skipUntil,
switchMap,
takeUntil,
takeWhile,
withLatestFrom,
} from "rxjs";
import { Simplify } from "type-fest";
@ -23,17 +24,24 @@ import { Simplify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import {
OnDependency,
SingleUserDependency,
UserBound,
UserDependency,
} from "@bitwarden/common/tools/dependencies";
import { IntegrationId, IntegrationMetadata } from "@bitwarden/common/tools/integration";
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
import { PaddedDataPacker } from "@bitwarden/common/tools/state/padded-data-packer";
import { isDynamic } from "@bitwarden/common/tools/state/state-constraints-dependency";
import { UserEncryptor } from "@bitwarden/common/tools/state/user-encryptor.abstraction";
import { UserKeyEncryptor } from "@bitwarden/common/tools/state/user-key-encryptor";
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
import { UserId } from "@bitwarden/common/types/guid";
import { Randomizer } from "../abstractions";
import { Generators, getForwarderConfiguration, toCredentialGeneratorConfiguration } from "../data";
@ -74,6 +82,8 @@ type Generate$Dependencies = Simplify<Partial<OnDependency> & Partial<UserDepend
type Algorithms$Dependencies = Partial<UserDependency>;
const OPTIONS_FRAME_SIZE = 512;
export class CredentialGeneratorService {
constructor(
private randomizer: Randomizer,
@ -81,6 +91,8 @@ export class CredentialGeneratorService {
private policyService: PolicyService,
private apiService: ApiService,
private i18nService: I18nService,
private readonly encryptService: EncryptService,
private cryptoService: CryptoService,
) {}
private getDependencyProvider(): GeneratorDependencyProvider {
@ -247,6 +259,21 @@ export class CredentialGeneratorService {
return info;
}
private encryptor$(userId: UserId) {
const packer = new PaddedDataPacker(OPTIONS_FRAME_SIZE);
const encryptor$ = this.cryptoService.userKey$(userId).pipe(
// complete when the account locks
takeWhile((key) => !!key),
map((key) => {
const encryptor = new UserKeyEncryptor(userId, this.encryptService, key, packer);
return { userId, encryptor } satisfies UserBound<"encryptor", UserEncryptor>;
}),
);
return encryptor$;
}
/** Get the settings for the provided configuration
* @param configuration determines which generator's settings are loaded
* @param dependencies.userId$ identifies the user to which the settings are bound.
@ -260,16 +287,16 @@ export class CredentialGeneratorService {
dependencies?: Settings$Dependencies,
) {
const userId$ = dependencies?.userId$ ?? this.stateProvider.activeUserId$;
const completion$ = userId$.pipe(ignoreElements(), endWith(true));
const state$ = userId$.pipe(
filter((userId) => !!userId),
distinctUntilChanged(),
switchMap((userId) => {
const state$ = this.stateProvider
.getUserState$(configuration.settings.account, userId)
.pipe(takeUntil(completion$));
const state$ = new UserStateSubject(
configuration.settings.account,
(key) => this.stateProvider.getUser(userId, key),
{ singleUserEncryptor$: this.encryptor$(userId) },
);
return state$;
}),
map((settings) => settings ?? structuredClone(configuration.settings.initial)),
@ -332,7 +359,7 @@ export class CredentialGeneratorService {
const subject = new UserStateSubject(
configuration.settings.account,
(key) => this.stateProvider.getUser(userId, key),
{ ...dependencies, constraints$ },
{ constraints$, singleUserEncryptor$: this.encryptor$(userId) },
);
return subject;

View File

@ -1,6 +1,7 @@
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
import { ObjectKey } from "@bitwarden/common/tools/state/subject-key";
import { Constraints } from "@bitwarden/common/tools/types";
import { Randomizer } from "../abstractions";
@ -97,10 +98,10 @@ export type CredentialGeneratorConfiguration<Settings, Policy> = CredentialGener
constraints: Constraints<Settings>;
/** storage location for account-global settings */
account: UserKeyDefinition<Settings>;
account: UserKeyDefinition<Settings> | ObjectKey<Settings>;
/** storage location for *plaintext* settings imports */
import?: UserKeyDefinition<Settings>;
import?: UserKeyDefinition<Settings> | ObjectKey<Settings, Record<string, never>, Settings>;
};
/** defines how to construct policy for this settings instance */