1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

Merge branch 'main' into auth/pm-8111/browser-refresh-login-component

This commit is contained in:
Alec Rippberger
2024-10-17 16:30:15 -05:00
105 changed files with 1492 additions and 478 deletions

View File

@@ -51,9 +51,13 @@ describe("ORGANIZATIONS state", () => {
keyConnectorEnabled: false,
keyConnectorUrl: "kcu",
accessSecretsManager: false,
limitCollectionCreation: false,
limitCollectionDeletion: false,
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: false,
allowAdminAccessToAllCollectionItems: false,
familySponsorshipLastSyncDate: new Date(),
userIsManagedByOrganization: false,
},
};
const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult)));

View File

@@ -52,8 +52,12 @@ export class OrganizationData {
familySponsorshipValidUntil?: Date;
familySponsorshipToDelete?: boolean;
accessSecretsManager: boolean;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;
userIsManagedByOrganization: boolean;
constructor(
response?: ProfileOrganizationResponse,
@@ -110,8 +114,12 @@ export class OrganizationData {
this.familySponsorshipValidUntil = response.familySponsorshipValidUntil;
this.familySponsorshipToDelete = response.familySponsorshipToDelete;
this.accessSecretsManager = response.accessSecretsManager;
this.limitCollectionCreation = response.limitCollectionCreation;
this.limitCollectionDeletion = response.limitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = response.limitCollectionCreationDeletion;
this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems;
this.userIsManagedByOrganization = response.userIsManagedByOrganization;
this.isMember = options.isMember;
this.isProviderUser = options.isProviderUser;

View File

@@ -68,11 +68,21 @@ export class Organization {
/**
* Refers to the ability for an organization to limit collection creation and deletion to owners and admins only
*/
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
/**
* Refers to the ability for an owner/admin to access all collection items, regardless of assigned collections
*/
allowAdminAccessToAllCollectionItems: boolean;
/**
* Indicates if this organization manages the user.
* A user is considered managed by an organization if their email domain
* matches one of the verified domains of that organization, and the user is a member of it.
*/
userIsManagedByOrganization: boolean;
constructor(obj?: OrganizationData) {
if (obj == null) {
@@ -125,8 +135,12 @@ export class Organization {
this.familySponsorshipValidUntil = obj.familySponsorshipValidUntil;
this.familySponsorshipToDelete = obj.familySponsorshipToDelete;
this.accessSecretsManager = obj.accessSecretsManager;
this.limitCollectionCreation = obj.limitCollectionCreation;
this.limitCollectionDeletion = obj.limitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = obj.limitCollectionCreationDeletion;
this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems;
this.userIsManagedByOrganization = obj.userIsManagedByOrganization;
}
get canAccess() {
@@ -163,9 +177,7 @@ export class Organization {
}
get canCreateNewCollections() {
return (
!this.limitCollectionCreationDeletion || this.isAdmin || this.permissions.createNewCollections
);
return !this.limitCollectionCreation || this.isAdmin || this.permissions.createNewCollections;
}
get canEditAnyCollection() {

View File

@@ -1,4 +1,7 @@
export class OrganizationCollectionManagementUpdateRequest {
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCreateDeleteOwnerAdmin: boolean;
allowAdminAccessToAllCollectionItems: boolean;
}

View File

@@ -32,6 +32,9 @@ export class OrganizationResponse extends BaseResponse {
smServiceAccounts?: number;
maxAutoscaleSmSeats?: number;
maxAutoscaleSmServiceAccounts?: number;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;
@@ -69,6 +72,9 @@ export class OrganizationResponse extends BaseResponse {
this.smServiceAccounts = this.getResponseProperty("SmServiceAccounts");
this.maxAutoscaleSmSeats = this.getResponseProperty("MaxAutoscaleSmSeats");
this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts");
this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation");
this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion");
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = this.getResponseProperty(
"LimitCollectionCreationDeletion",
);

View File

@@ -49,8 +49,12 @@ export class ProfileOrganizationResponse extends BaseResponse {
familySponsorshipValidUntil?: Date;
familySponsorshipToDelete?: boolean;
accessSecretsManager: boolean;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;
userIsManagedByOrganization: boolean;
constructor(response: any) {
super(response);
@@ -109,11 +113,15 @@ export class ProfileOrganizationResponse extends BaseResponse {
}
this.familySponsorshipToDelete = this.getResponseProperty("FamilySponsorshipToDelete");
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation");
this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion");
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = this.getResponseProperty(
"LimitCollectionCreationDeletion",
);
this.allowAdminAccessToAllCollectionItems = this.getResponseProperty(
"AllowAdminAccessToAllCollectionItems",
);
this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization");
}
}

View File

@@ -13,6 +13,7 @@ export type KdfConfig = PBKDF2KdfConfig | Argon2KdfConfig;
*/
export class PBKDF2KdfConfig {
static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000);
static PRELOGIN_ITERATIONS = new RangeWithDefault(5000, 2_000_000, 600_000);
kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256;
iterations: number;
@@ -21,10 +22,10 @@ export class PBKDF2KdfConfig {
}
/**
* Validates the PBKDF2 KDF configuration.
* Validates the PBKDF2 KDF configuration for updating the KDF config.
* A Valid PBKDF2 KDF configuration has KDF iterations between the 600_000 and 2_000_000.
*/
validateKdfConfig(): void {
validateKdfConfigForSetting(): void {
if (!PBKDF2KdfConfig.ITERATIONS.inRange(this.iterations)) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.ITERATIONS.min} and ${PBKDF2KdfConfig.ITERATIONS.max}`,
@@ -32,6 +33,18 @@ export class PBKDF2KdfConfig {
}
}
/**
* Validates the PBKDF2 KDF configuration for pre-login.
* A Valid PBKDF2 KDF configuration has KDF iterations between the 5000 and 2_000_000.
*/
validateKdfConfigForPrelogin(): void {
if (!PBKDF2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
);
}
}
static fromJSON(json: Jsonify<PBKDF2KdfConfig>): PBKDF2KdfConfig {
return new PBKDF2KdfConfig(json.iterations);
}
@@ -44,6 +57,11 @@ export class Argon2KdfConfig {
static MEMORY = new RangeWithDefault(16, 1024, 64);
static PARALLELISM = new RangeWithDefault(1, 16, 4);
static ITERATIONS = new RangeWithDefault(2, 10, 3);
static PRELOGIN_MEMORY = Argon2KdfConfig.MEMORY;
static PRELOGIN_PARALLELISM = Argon2KdfConfig.PARALLELISM;
static PRELOGIN_ITERATIONS = Argon2KdfConfig.ITERATIONS;
kdfType: KdfType.Argon2id = KdfType.Argon2id;
iterations: number;
memory: number;
@@ -56,10 +74,10 @@ export class Argon2KdfConfig {
}
/**
* Validates the Argon2 KDF configuration.
* Validates the Argon2 KDF configuration for updating the KDF config.
* A Valid Argon2 KDF configuration has iterations between 2 and 10, memory between 16mb and 1024mb, and parallelism between 1 and 16.
*/
validateKdfConfig(): void {
validateKdfConfigForSetting(): void {
if (!Argon2KdfConfig.ITERATIONS.inRange(this.iterations)) {
throw new Error(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
@@ -79,6 +97,29 @@ export class Argon2KdfConfig {
}
}
/**
* Validates the Argon2 KDF configuration for pre-login.
*/
validateKdfConfigForPrelogin(): void {
if (!Argon2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) {
throw new Error(
`Argon2 iterations must be between ${Argon2KdfConfig.PRELOGIN_ITERATIONS.min} and ${Argon2KdfConfig.PRELOGIN_ITERATIONS.max}`,
);
}
if (!Argon2KdfConfig.PRELOGIN_MEMORY.inRange(this.memory)) {
throw new Error(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`,
);
}
if (!Argon2KdfConfig.PRELOGIN_PARALLELISM.inRange(this.parallelism)) {
throw new Error(
`Argon2 parallelism must be between ${Argon2KdfConfig.PRELOGIN_PARALLELISM.min} and ${Argon2KdfConfig.PRELOGIN_PARALLELISM.max}.`,
);
}
}
static fromJSON(json: Jsonify<Argon2KdfConfig>): Argon2KdfConfig {
return new Argon2KdfConfig(json.iterations, json.memory, json.parallelism);
}

View File

@@ -58,41 +58,120 @@ describe("KdfConfigService", () => {
}
});
it("validateKdfConfig(): should validate the PBKDF2 KDF config", () => {
it("validateKdfConfigForSetting(): should validate the PBKDF2 KDF config", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
expect(() => kdfConfig.validateKdfConfig()).not.toThrow();
expect(() => kdfConfig.validateKdfConfigForSetting()).not.toThrow();
});
it("validateKdfConfig(): should validate the Argon2id KDF config", () => {
it("validateKdfConfigForSetting(): should validate the Argon2id KDF config", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
expect(() => kdfConfig.validateKdfConfig()).not.toThrow();
expect(() => kdfConfig.validateKdfConfigForSetting()).not.toThrow();
});
it("validateKdfConfig(): should throw an error for invalid PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(100);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
it("validateKdfConfigForSetting(): should throw an error for invalid PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(100000);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.ITERATIONS.min} and ${PBKDF2KdfConfig.ITERATIONS.max}`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 iterations", () => {
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(11, 64, 4);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 memory", () => {
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 1025, 4);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 parallelism", () => {
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 parallelism must be between ${Argon2KdfConfig.PARALLELISM.min} and ${Argon2KdfConfig.PARALLELISM.max}`,
);
});
it("validateKdfConfigForPrelogin(): should validate the PBKDF2 KDF config", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).not.toThrow();
});
it("validateKdfConfigForPrelogin(): should validate the Argon2id KDF config", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).not.toThrow();
});
it("validateKdfConfigForPrelogin(): should throw an error for too low PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(
PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(
PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max + 1,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
Argon2KdfConfig.ITERATIONS.min - 1,
64,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
Argon2KdfConfig.PRELOGIN_ITERATIONS.max + 1,
64,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
3,
Argon2KdfConfig.PRELOGIN_MEMORY.min - 1,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
3,
Argon2KdfConfig.PRELOGIN_MEMORY.max + 1,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 parallelism must be between ${Argon2KdfConfig.PRELOGIN_PARALLELISM.min} and ${Argon2KdfConfig.PRELOGIN_PARALLELISM.max}`,
);
});
});

View File

@@ -362,7 +362,8 @@ describe("KeyConnectorService", () => {
familySponsorshipValidUntil: null,
familySponsorshipToDelete: null,
accessSecretsManager: false,
limitCollectionCreationDeletion: true,
limitCollectionCreation: true,
limitCollectionDeletion: true,
allowAdminAccessToAllCollectionItems: true,
flexibleCollections: false,
object: "profileOrganization",

View File

@@ -36,6 +36,7 @@ export enum FeatureFlag {
Pm3478RefactorOrganizationUserApi = "pm-3478-refactor-organizationuser-api",
AccessIntelligence = "pm-13227-access-intelligence",
Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions",
LimitCollectionCreationDeletionSplit = "pm-10863-limit-collection-creation-deletion-split",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -82,6 +83,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.Pm3478RefactorOrganizationUserApi]: FALSE,
[FeatureFlag.AccessIntelligence]: FALSE,
[FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE,
[FeatureFlag.LimitCollectionCreationDeletionSplit]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;

View File

@@ -21,7 +21,6 @@ export class ProfileResponse extends BaseResponse {
securityStamp: string;
forcePasswordReset: boolean;
usesKeyConnector: boolean;
managedByOrganizationId?: string | null;
organizations: ProfileOrganizationResponse[] = [];
providers: ProfileProviderResponse[] = [];
providerOrganizations: ProfileProviderOrganizationResponse[] = [];
@@ -43,7 +42,6 @@ export class ProfileResponse extends BaseResponse {
this.securityStamp = this.getResponseProperty("SecurityStamp");
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset") ?? false;
this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector") ?? false;
this.managedByOrganizationId = this.getResponseProperty("ManagedByOrganizationId");
const organizations = this.getResponseProperty("Organizations");
if (organizations != null) {

View File

@@ -123,6 +123,14 @@ export class DefaultConfigService implements ConfigService {
serverConfig: ServerConfig | null,
flag: Flag,
) {
if (
flag === FeatureFlag.ExtensionRefresh ||
flag === FeatureFlag.UnauthenticatedExtensionUIRefresh ||
flag === FeatureFlag.EmailVerification
) {
return true as FeatureFlagValueType<Flag>;
}
if (serverConfig?.featureStates == null || serverConfig.featureStates[flag] == null) {
return DefaultFeatureFlagValue[flag];
}