-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-5974] introduce ForwarderGeneratorStrategy (#8207)
* update defaults to include `website` parameter * update utilities tests to include `website` parameter
- Loading branch information
1 parent
a5c78fb
commit c731831
Showing
19 changed files
with
610 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
libs/common/src/tools/generator/username/forwarder-generator-strategy.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { mock } from "jest-mock-extended"; | ||
|
||
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; | ||
import { CryptoService } from "../../../platform/abstractions/crypto.service"; | ||
import { EncryptService } from "../../../platform/abstractions/encrypt.service"; | ||
import { StateProvider } from "../../../platform/state"; | ||
import { UserId } from "../../../types/guid"; | ||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator"; | ||
import { DUCK_DUCK_GO_FORWARDER } from "../key-definitions"; | ||
import { SecretState } from "../state/secret-state"; | ||
|
||
import { ForwarderGeneratorStrategy } from "./forwarder-generator-strategy"; | ||
import { ApiOptions } from "./options/forwarder-options"; | ||
|
||
class TestForwarder extends ForwarderGeneratorStrategy<ApiOptions> { | ||
constructor( | ||
encryptService: EncryptService, | ||
keyService: CryptoService, | ||
stateProvider: StateProvider, | ||
) { | ||
super(encryptService, keyService, stateProvider); | ||
} | ||
|
||
get key() { | ||
// arbitrary. | ||
return DUCK_DUCK_GO_FORWARDER; | ||
} | ||
} | ||
|
||
const SomeUser = "some user" as UserId; | ||
const AnotherUser = "another user" as UserId; | ||
|
||
describe("ForwarderGeneratorStrategy", () => { | ||
const encryptService = mock<EncryptService>(); | ||
const keyService = mock<CryptoService>(); | ||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); | ||
|
||
describe("durableState", () => { | ||
it("constructs a secret state", () => { | ||
const strategy = new TestForwarder(encryptService, keyService, stateProvider); | ||
|
||
const result = strategy.durableState(SomeUser); | ||
|
||
expect(result).toBeInstanceOf(SecretState); | ||
}); | ||
|
||
it("returns the same secret state for a single user", () => { | ||
const strategy = new TestForwarder(encryptService, keyService, stateProvider); | ||
|
||
const firstResult = strategy.durableState(SomeUser); | ||
const secondResult = strategy.durableState(SomeUser); | ||
|
||
expect(firstResult).toBe(secondResult); | ||
}); | ||
|
||
it("returns a different secret state for a different user", () => { | ||
const strategy = new TestForwarder(encryptService, keyService, stateProvider); | ||
|
||
const firstResult = strategy.durableState(SomeUser); | ||
const secondResult = strategy.durableState(AnotherUser); | ||
|
||
expect(firstResult).not.toBe(secondResult); | ||
}); | ||
}); | ||
|
||
it("evaluator returns the default policy evaluator", () => { | ||
const strategy = new TestForwarder(null, null, null); | ||
|
||
const result = strategy.evaluator(null); | ||
|
||
expect(result).toBeInstanceOf(DefaultPolicyEvaluator); | ||
}); | ||
}); |
73 changes: 73 additions & 0 deletions
73
libs/common/src/tools/generator/username/forwarder-generator-strategy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { PolicyType } from "../../../admin-console/enums"; | ||
import { Policy } from "../../../admin-console/models/domain/policy"; | ||
import { CryptoService } from "../../../platform/abstractions/crypto.service"; | ||
import { EncryptService } from "../../../platform/abstractions/encrypt.service"; | ||
import { KeyDefinition, StateProvider } from "../../../platform/state"; | ||
import { UserId } from "../../../types/guid"; | ||
import { GeneratorStrategy } from "../abstractions"; | ||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator"; | ||
import { NoPolicy } from "../no-policy"; | ||
import { PaddedDataPacker } from "../state/padded-data-packer"; | ||
import { SecretClassifier } from "../state/secret-classifier"; | ||
import { SecretState } from "../state/secret-state"; | ||
import { UserKeyEncryptor } from "../state/user-key-encryptor"; | ||
|
||
import { ApiOptions } from "./options/forwarder-options"; | ||
|
||
const ONE_MINUTE = 60 * 1000; | ||
const OPTIONS_FRAME_SIZE = 512; | ||
|
||
/** An email forwarding service configurable through an API. */ | ||
export abstract class ForwarderGeneratorStrategy< | ||
Options extends ApiOptions, | ||
> extends GeneratorStrategy<Options, NoPolicy> { | ||
/** Initializes the generator strategy | ||
* @param encryptService protects sensitive forwarder options | ||
* @param keyService looks up the user key when protecting data. | ||
* @param stateProvider creates the durable state for options storage | ||
*/ | ||
constructor( | ||
private readonly encryptService: EncryptService, | ||
private readonly keyService: CryptoService, | ||
private stateProvider: StateProvider, | ||
) { | ||
super(); | ||
// Uses password generator since there aren't policies | ||
// specific to usernames. | ||
this.policy = PolicyType.PasswordGenerator; | ||
|
||
this.cache_ms = ONE_MINUTE; | ||
} | ||
|
||
private durableStates = new Map<UserId, SecretState<Options, Record<string, never>>>(); | ||
|
||
/** {@link GeneratorStrategy.durableState} */ | ||
durableState = (userId: UserId) => { | ||
let state = this.durableStates.get(userId); | ||
|
||
if (!state) { | ||
const encryptor = this.createEncryptor(); | ||
state = SecretState.from(userId, this.key, this.stateProvider, encryptor); | ||
this.durableStates.set(userId, state); | ||
} | ||
|
||
return state; | ||
}; | ||
|
||
private createEncryptor() { | ||
// always exclude request properties | ||
const classifier = SecretClassifier.allSecret<Options>().exclude("website"); | ||
|
||
// construct the encryptor | ||
const packer = new PaddedDataPacker(OPTIONS_FRAME_SIZE); | ||
return new UserKeyEncryptor(this.encryptService, this.keyService, classifier, packer); | ||
} | ||
|
||
/** Determine where forwarder configuration is stored */ | ||
protected abstract readonly key: KeyDefinition<Options>; | ||
|
||
/** {@link GeneratorStrategy.evaluator} */ | ||
evaluator = (_policy: Policy) => { | ||
return new DefaultPolicyEvaluator<Options>(); | ||
}; | ||
} |
Oops, something went wrong.