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

[PM-20633] rename personal ownership (#15228)

* sensible renames

* renames

* clean up comments
This commit is contained in:
Brandon Treston
2025-06-24 09:31:40 -04:00
committed by GitHub
parent fa23a905e0
commit 1c237a3753
37 changed files with 170 additions and 162 deletions

View File

@@ -1056,7 +1056,7 @@ export default class NotificationBackground {
this.accountService.activeAccount$.pipe( this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
), ),
); );

View File

@@ -228,10 +228,10 @@ describe("VaultPopupListFiltersService", () => {
}); });
}); });
describe("PersonalOwnership policy", () => { describe("OrganizationDataOwnership policy", () => {
it('calls policyAppliesToUser$ with "PersonalOwnership"', () => { it('calls policyAppliesToUser$ with "OrganizationDataOwnership"', () => {
expect(policyService.policyAppliesToUser$).toHaveBeenCalledWith( expect(policyService.policyAppliesToUser$).toHaveBeenCalledWith(
PolicyType.PersonalOwnership, PolicyType.OrganizationDataOwnership,
"userId", "userId",
); );
}); });

View File

@@ -293,30 +293,30 @@ export class VaultPopupListFiltersService {
switchMap((userId) => switchMap((userId) =>
combineLatest([ combineLatest([
this.organizationService.memberOrganizations$(userId), this.organizationService.memberOrganizations$(userId),
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
]), ]),
), ),
map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [ map(([orgs, organizationDataOwnership]): [Organization[], boolean] => [
orgs.sort(Utils.getSortFunction(this.i18nService, "name")), orgs.sort(Utils.getSortFunction(this.i18nService, "name")),
personalOwnershipApplies, organizationDataOwnership,
]), ]),
map(([orgs, personalOwnershipApplies]) => { map(([orgs, organizationDataOwnership]) => {
// When there are no organizations return an empty array, // When there are no organizations return an empty array,
// resulting in the org filter being hidden // resulting in the org filter being hidden
if (!orgs.length) { if (!orgs.length) {
return []; return [];
} }
// When there is only one organization and personal ownership policy applies, // When there is only one organization and organization data ownership policy applies,
// return an empty array, resulting in the org filter being hidden // return an empty array, resulting in the org filter being hidden
if (orgs.length === 1 && personalOwnershipApplies) { if (orgs.length === 1 && organizationDataOwnership) {
return []; return [];
} }
const myVaultOrg: ChipSelectOption<Organization>[] = []; const myVaultOrg: ChipSelectOption<Organization>[] = [];
// Only add "My vault" if personal ownership policy does not apply // Only add "My vault" if organization data ownership policy does not apply
if (!personalOwnershipApplies) { if (!organizationDataOwnership) {
myVaultOrg.push({ myVaultOrg.push({
value: { id: MY_VAULT_ID } as Organization, value: { id: MY_VAULT_ID } as Organization,
label: this.i18nService.t("myVault"), label: this.i18nService.t("myVault"),

View File

@@ -147,7 +147,7 @@ export class EncryptedMessageHandlerService {
const policyApplies$ = this.accountService.activeAccount$.pipe( const policyApplies$ = this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
); );

View File

@@ -16,7 +16,9 @@ import { ToastService } from "@bitwarden/components";
}) })
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
get show() { get show() {
const hiddenDisplayModes: DisplayMode[] = ["singleOrganizationAndPersonalOwnershipPolicies"]; const hiddenDisplayModes: DisplayMode[] = [
"singleOrganizationAndOrganizatonDataOwnershipPolicies",
];
return ( return (
!this.hide && !this.hide &&
this.organizations.length > 0 && this.organizations.length > 0 &&

View File

@@ -8,7 +8,7 @@
[activeFilter]="activeFilter" [activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes" [collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations" [organizations]="organizations"
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy" [activeOrganizationDataOwnership]="activeOrganizationDataOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy" [activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)" (onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)" (onFilterChange)="applyFilter($event)"

View File

@@ -3,7 +3,7 @@ export { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export { DisableSendPolicy } from "./disable-send.component"; export { DisableSendPolicy } from "./disable-send.component";
export { MasterPasswordPolicy } from "./master-password.component"; export { MasterPasswordPolicy } from "./master-password.component";
export { PasswordGeneratorPolicy } from "./password-generator.component"; export { PasswordGeneratorPolicy } from "./password-generator.component";
export { PersonalOwnershipPolicy } from "./personal-ownership.component"; export { OrganizationDataOwnershipPolicy } from "./organization-data-ownership.component";
export { RequireSsoPolicy } from "./require-sso.component"; export { RequireSsoPolicy } from "./require-sso.component";
export { ResetPasswordPolicy } from "./reset-password.component"; export { ResetPasswordPolicy } from "./reset-password.component";
export { SendOptionsPolicy } from "./send-options.component"; export { SendOptionsPolicy } from "./send-options.component";

View File

@@ -0,0 +1,19 @@
import { Component } from "@angular/core";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export class OrganizationDataOwnershipPolicy extends BasePolicy {
name = "organizationDataOwnership";
description = "personalOwnershipPolicyDesc";
type = PolicyType.OrganizationDataOwnership;
component = OrganizationDataOwnershipPolicyComponent;
}
@Component({
selector: "policy-organization-data-ownership",
templateUrl: "organization-data-ownership.component.html",
standalone: false,
})
export class OrganizationDataOwnershipPolicyComponent extends BasePolicyComponent {}

View File

@@ -1,19 +0,0 @@
import { Component } from "@angular/core";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export class PersonalOwnershipPolicy extends BasePolicy {
name = "personalOwnership";
description = "personalOwnershipPolicyDesc";
type = PolicyType.PersonalOwnership;
component = PersonalOwnershipPolicyComponent;
}
@Component({
selector: "policy-personal-ownership",
templateUrl: "personal-ownership.component.html",
standalone: false,
})
export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {}

View File

@@ -4,8 +4,8 @@ import { LooseComponentsModule, SharedModule } from "../../../shared";
import { DisableSendPolicyComponent } from "./disable-send.component"; import { DisableSendPolicyComponent } from "./disable-send.component";
import { MasterPasswordPolicyComponent } from "./master-password.component"; import { MasterPasswordPolicyComponent } from "./master-password.component";
import { OrganizationDataOwnershipPolicyComponent } from "./organization-data-ownership.component";
import { PasswordGeneratorPolicyComponent } from "./password-generator.component"; import { PasswordGeneratorPolicyComponent } from "./password-generator.component";
import { PersonalOwnershipPolicyComponent } from "./personal-ownership.component";
import { PoliciesComponent } from "./policies.component"; import { PoliciesComponent } from "./policies.component";
import { PolicyEditComponent } from "./policy-edit.component"; import { PolicyEditComponent } from "./policy-edit.component";
import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component"; import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component";
@@ -22,7 +22,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat
DisableSendPolicyComponent, DisableSendPolicyComponent,
MasterPasswordPolicyComponent, MasterPasswordPolicyComponent,
PasswordGeneratorPolicyComponent, PasswordGeneratorPolicyComponent,
PersonalOwnershipPolicyComponent, OrganizationDataOwnershipPolicyComponent,
RequireSsoPolicyComponent, RequireSsoPolicyComponent,
ResetPasswordPolicyComponent, ResetPasswordPolicyComponent,
SendOptionsPolicyComponent, SendOptionsPolicyComponent,
@@ -37,7 +37,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat
DisableSendPolicyComponent, DisableSendPolicyComponent,
MasterPasswordPolicyComponent, MasterPasswordPolicyComponent,
PasswordGeneratorPolicyComponent, PasswordGeneratorPolicyComponent,
PersonalOwnershipPolicyComponent, OrganizationDataOwnershipPolicyComponent,
RequireSsoPolicyComponent, RequireSsoPolicyComponent,
ResetPasswordPolicyComponent, ResetPasswordPolicyComponent,
SendOptionsPolicyComponent, SendOptionsPolicyComponent,

View File

@@ -34,7 +34,7 @@ import {
DisableSendPolicy, DisableSendPolicy,
MasterPasswordPolicy, MasterPasswordPolicy,
PasswordGeneratorPolicy, PasswordGeneratorPolicy,
PersonalOwnershipPolicy, OrganizationDataOwnershipPolicy,
RequireSsoPolicy, RequireSsoPolicy,
ResetPasswordPolicy, ResetPasswordPolicy,
SendOptionsPolicy, SendOptionsPolicy,
@@ -243,7 +243,7 @@ export class AppComponent implements OnDestroy, OnInit {
new PasswordGeneratorPolicy(), new PasswordGeneratorPolicy(),
new SingleOrgPolicy(), new SingleOrgPolicy(),
new RequireSsoPolicy(), new RequireSsoPolicy(),
new PersonalOwnershipPolicy(), new OrganizationDataOwnershipPolicy(),
new DisableSendPolicy(), new DisableSendPolicy(),
new SendOptionsPolicy(), new SendOptionsPolicy(),
]); ]);

View File

@@ -175,7 +175,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
merge( merge(
this.policyService.policiesByType$(PolicyType.SingleOrg, userId).pipe(getFirstPolicy), this.policyService.policiesByType$(PolicyType.SingleOrg, userId).pipe(getFirstPolicy),
this.policyService this.policyService
.policiesByType$(PolicyType.PersonalOwnership, userId) .policiesByType$(PolicyType.OrganizationDataOwnership, userId)
.pipe(getFirstPolicy), .pipe(getFirstPolicy),
), ),
), ),
@@ -268,7 +268,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
this.accountService.activeAccount$.pipe( this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
), ),
); );

View File

@@ -36,7 +36,7 @@ describe("vault filter service", () => {
let folderViews: ReplaySubject<FolderView[]>; let folderViews: ReplaySubject<FolderView[]>;
let collectionViews: ReplaySubject<CollectionView[]>; let collectionViews: ReplaySubject<CollectionView[]>;
let cipherViews: ReplaySubject<CipherView[]>; let cipherViews: ReplaySubject<CipherView[]>;
let personalOwnershipPolicy: ReplaySubject<boolean>; let organizationDataOwnershipPolicy: ReplaySubject<boolean>;
let singleOrgPolicy: ReplaySubject<boolean>; let singleOrgPolicy: ReplaySubject<boolean>;
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
@@ -59,15 +59,15 @@ describe("vault filter service", () => {
folderViews = new ReplaySubject<FolderView[]>(1); folderViews = new ReplaySubject<FolderView[]>(1);
collectionViews = new ReplaySubject<CollectionView[]>(1); collectionViews = new ReplaySubject<CollectionView[]>(1);
cipherViews = new ReplaySubject<CipherView[]>(1); cipherViews = new ReplaySubject<CipherView[]>(1);
personalOwnershipPolicy = new ReplaySubject<boolean>(1); organizationDataOwnershipPolicy = new ReplaySubject<boolean>(1);
singleOrgPolicy = new ReplaySubject<boolean>(1); singleOrgPolicy = new ReplaySubject<boolean>(1);
organizationService.memberOrganizations$.mockReturnValue(organizations); organizationService.memberOrganizations$.mockReturnValue(organizations);
folderService.folderViews$.mockReturnValue(folderViews); folderService.folderViews$.mockReturnValue(folderViews);
collectionService.decryptedCollections$ = collectionViews; collectionService.decryptedCollections$ = collectionViews;
policyService.policyAppliesToUser$ policyService.policyAppliesToUser$
.calledWith(PolicyType.PersonalOwnership, mockUserId) .calledWith(PolicyType.OrganizationDataOwnership, mockUserId)
.mockReturnValue(personalOwnershipPolicy); .mockReturnValue(organizationDataOwnershipPolicy);
policyService.policyAppliesToUser$ policyService.policyAppliesToUser$
.calledWith(PolicyType.SingleOrg, mockUserId) .calledWith(PolicyType.SingleOrg, mockUserId)
.mockReturnValue(singleOrgPolicy); .mockReturnValue(singleOrgPolicy);
@@ -113,7 +113,7 @@ describe("vault filter service", () => {
beforeEach(() => { beforeEach(() => {
const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")]; const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")];
organizations.next(storedOrgs); organizations.next(storedOrgs);
personalOwnershipPolicy.next(false); organizationDataOwnershipPolicy.next(false);
singleOrgPolicy.next(false); singleOrgPolicy.next(false);
}); });
@@ -125,8 +125,8 @@ describe("vault filter service", () => {
expect(tree.children.find((o) => o.node.name === "org2")); expect(tree.children.find((o) => o.node.name === "org2"));
}); });
it("hides My Vault if personal ownership policy is enabled", async () => { it("hides My Vault if organization data ownership policy is enabled", async () => {
personalOwnershipPolicy.next(true); organizationDataOwnershipPolicy.next(true);
const tree = await firstValueFrom(vaultFilterService.organizationTree$); const tree = await firstValueFrom(vaultFilterService.organizationTree$);
@@ -144,9 +144,9 @@ describe("vault filter service", () => {
expect(tree.children.find((o) => o.node.id === "MyVault")); expect(tree.children.find((o) => o.node.id === "MyVault"));
}); });
it("returns 1 organization if both single organization and personal ownership policies are enabled", async () => { it("returns 1 organization if both single organization and organization data ownership policies are enabled", async () => {
singleOrgPolicy.next(true); singleOrgPolicy.next(true);
personalOwnershipPolicy.next(true); organizationDataOwnershipPolicy.next(true);
const tree = await firstValueFrom(vaultFilterService.organizationTree$); const tree = await firstValueFrom(vaultFilterService.organizationTree$);

View File

@@ -67,12 +67,12 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
), ),
this.activeUserId$.pipe( this.activeUserId$.pipe(
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
), ),
]).pipe( ]).pipe(
switchMap(([orgs, singleOrgPolicy, personalOwnershipPolicy]) => switchMap(([orgs, singleOrgPolicy, organizationDataOwnershipPolicy]) =>
this.buildOrganizationTree(orgs, singleOrgPolicy, personalOwnershipPolicy), this.buildOrganizationTree(orgs, singleOrgPolicy, organizationDataOwnershipPolicy),
), ),
); );
@@ -166,10 +166,10 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
protected async buildOrganizationTree( protected async buildOrganizationTree(
orgs: Organization[], orgs: Organization[],
singleOrgPolicy: boolean, singleOrgPolicy: boolean,
personalOwnershipPolicy: boolean, organizationDataOwnershipPolicy: boolean,
): Promise<TreeNode<OrganizationFilter>> { ): Promise<TreeNode<OrganizationFilter>> {
const headNode = this.getOrganizationFilterHead(); const headNode = this.getOrganizationFilterHead();
if (!personalOwnershipPolicy) { if (!organizationDataOwnershipPolicy) {
const myVaultNode = this.getOrganizationFilterMyVault(); const myVaultNode = this.getOrganizationFilterMyVault();
headNode.children.push(myVaultNode); headNode.children.push(myVaultNode);
} }

View File

@@ -166,7 +166,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
.pipe( .pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )

View File

@@ -128,18 +128,18 @@ describe("AdminConsoleCipherFormConfigService", () => {
expect(result.admin).toBe(true); expect(result.admin).toBe(true);
}); });
it("sets `allowPersonalOwnership`", async () => { it("sets `organizationDataOwnershipDisabled`", async () => {
policyAppliesToUser$.next(true); policyAppliesToUser$.next(true);
let result = await adminConsoleConfigService.buildConfig("clone", cipherId); let result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(result.allowPersonalOwnership).toBe(false); expect(result.organizationDataOwnershipDisabled).toBe(false);
policyAppliesToUser$.next(false); policyAppliesToUser$.next(false);
result = await adminConsoleConfigService.buildConfig("clone", cipherId); result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(result.allowPersonalOwnership).toBe(true); expect(result.organizationDataOwnershipDisabled).toBe(true);
}); });
it("disables personal ownership when not cloning", async () => { it("disables personal ownership when not cloning", async () => {
@@ -147,15 +147,15 @@ describe("AdminConsoleCipherFormConfigService", () => {
let result = await adminConsoleConfigService.buildConfig("add", cipherId); let result = await adminConsoleConfigService.buildConfig("add", cipherId);
expect(result.allowPersonalOwnership).toBe(false); expect(result.organizationDataOwnershipDisabled).toBe(false);
result = await adminConsoleConfigService.buildConfig("edit", cipherId); result = await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(result.allowPersonalOwnership).toBe(false); expect(result.organizationDataOwnershipDisabled).toBe(false);
result = await adminConsoleConfigService.buildConfig("clone", cipherId); result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(result.allowPersonalOwnership).toBe(true); expect(result.organizationDataOwnershipDisabled).toBe(true);
}); });
it("returns all ciphers when cloning a cipher", async () => { it("returns all ciphers when cloning a cipher", async () => {

View File

@@ -31,10 +31,10 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
private apiService: ApiService = inject(ApiService); private apiService: ApiService = inject(ApiService);
private accountService: AccountService = inject(AccountService); private accountService: AccountService = inject(AccountService);
private allowPersonalOwnership$ = this.accountService.activeAccount$.pipe( private organizationDataOwnershipDisabled$ = this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
map((p) => !p), map((p) => !p),
); );
@@ -69,11 +69,11 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
cipherId?: CipherId, cipherId?: CipherId,
cipherType?: CipherType, cipherType?: CipherType,
): Promise<CipherFormConfig> { ): Promise<CipherFormConfig> {
const [organization, allowPersonalOwnership, allOrganizations, allCollections] = const [organization, organizationDataOwnershipDisabled, allOrganizations, allCollections] =
await firstValueFrom( await firstValueFrom(
combineLatest([ combineLatest([
this.organization$, this.organization$,
this.allowPersonalOwnership$, this.organizationDataOwnershipDisabled$,
this.allOrganizations$, this.allOrganizations$,
this.allCollections$, this.allCollections$,
]), ]),
@@ -84,13 +84,14 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
const organizations = mode === "clone" ? allOrganizations : [organization]; const organizations = mode === "clone" ? allOrganizations : [organization];
// Only allow the user to assign to their personal vault when cloning and // Only allow the user to assign to their personal vault when cloning and
// the policies are enabled for it. // the policies are enabled for it.
const allowPersonalOwnershipOnlyForClone = mode === "clone" ? allowPersonalOwnership : false; const disableOrganizationDataOwnershipOnlyForClone =
mode === "clone" ? organizationDataOwnershipDisabled : false;
const cipher = await this.getCipher(cipherId, organization); const cipher = await this.getCipher(cipherId, organization);
return { return {
mode, mode,
cipherType: cipher?.type ?? cipherType ?? CipherType.Login, cipherType: cipher?.type ?? cipherType ?? CipherType.Login,
admin: organization.canEditAllCiphers ?? false, admin: organization.canEditAllCiphers ?? false,
allowPersonalOwnership: allowPersonalOwnershipOnlyForClone, organizationDataOwnershipDisabled: disableOrganizationDataOwnershipOnlyForClone,
originalCipher: cipher, originalCipher: cipher,
collections: allCollections, collections: allCollections,
organizations, organizations,

View File

@@ -5379,6 +5379,9 @@
} }
} }
}, },
"organizationDataOwnership": {
"message": "Enforce organization data ownership"
},
"personalOwnership": { "personalOwnership": {
"message": "Remove individual vault" "message": "Remove individual vault"
}, },

View File

@@ -14,7 +14,7 @@ export class FreeFamiliesSponsorshipPolicy extends BasePolicy {
} }
@Component({ @Component({
selector: "policy-personal-ownership", selector: "policy-free-families-sponsorship",
templateUrl: "free-families-sponsorship.component.html", templateUrl: "free-families-sponsorship.component.html",
standalone: false, standalone: false,
}) })

View File

@@ -20,5 +20,5 @@ export abstract class DeprecatedVaultFilterService {
buildCollapsedFilterNodes: () => Promise<Set<string>>; buildCollapsedFilterNodes: () => Promise<Set<string>>;
storeCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>; storeCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
checkForSingleOrganizationPolicy: () => Promise<boolean>; checkForSingleOrganizationPolicy: () => Promise<boolean>;
checkForPersonalOwnershipPolicy: () => Promise<boolean>; checkForOrganizationDataOwnershipPolicy: () => Promise<boolean>;
} }

View File

@@ -103,7 +103,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected componentName = ""; protected componentName = "";
protected destroy$ = new Subject<void>(); protected destroy$ = new Subject<void>();
protected writeableCollections: CollectionView[]; protected writeableCollections: CollectionView[];
private personalOwnershipPolicyAppliesToActiveUser: boolean; private organizationDataOwnershipAppliesToUser: boolean;
private previousCipherId: string; private previousCipherId: string;
get fido2CredentialCreationDateValue(): string { get fido2CredentialCreationDateValue(): string {
@@ -195,10 +195,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
.pipe( .pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
concatMap(async (policyAppliesToActiveUser) => { concatMap(async (policyAppliesToActiveUser) => {
this.personalOwnershipPolicyAppliesToActiveUser = policyAppliesToActiveUser; this.organizationDataOwnershipAppliesToUser = policyAppliesToActiveUser;
await this.init(); await this.init();
}), }),
takeUntil(this.destroy$), takeUntil(this.destroy$),
@@ -218,7 +218,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
if (this.ownershipOptions.length) { if (this.ownershipOptions.length) {
this.ownershipOptions = []; this.ownershipOptions = [];
} }
if (this.personalOwnershipPolicyAppliesToActiveUser) { if (this.organizationDataOwnershipAppliesToUser) {
this.allowPersonal = false; this.allowPersonal = false;
} else { } else {
const myEmail = await firstValueFrom( const myEmail = await firstValueFrom(

View File

@@ -15,7 +15,7 @@ export class OrganizationFilterComponent {
@Input() collapsedFilterNodes: Set<string>; @Input() collapsedFilterNodes: Set<string>;
@Input() organizations: Organization[]; @Input() organizations: Organization[];
@Input() activeFilter: VaultFilter; @Input() activeFilter: VaultFilter;
@Input() activePersonalOwnershipPolicy: boolean; @Input() activeOrganizationDataOwnership: boolean;
@Input() activeSingleOrganizationPolicy: boolean; @Input() activeSingleOrganizationPolicy: boolean;
@Output() onNodeCollapseStateChange: EventEmitter<ITreeNodeObject> = @Output() onNodeCollapseStateChange: EventEmitter<ITreeNodeObject> =
@@ -26,12 +26,12 @@ export class OrganizationFilterComponent {
let displayMode: DisplayMode = "organizationMember"; let displayMode: DisplayMode = "organizationMember";
if (this.organizations == null || this.organizations.length < 1) { if (this.organizations == null || this.organizations.length < 1) {
displayMode = "noOrganizations"; displayMode = "noOrganizations";
} else if (this.activePersonalOwnershipPolicy && !this.activeSingleOrganizationPolicy) { } else if (this.activeOrganizationDataOwnership && !this.activeSingleOrganizationPolicy) {
displayMode = "personalOwnershipPolicy"; displayMode = "organizationDataOwnershipPolicy";
} else if (!this.activePersonalOwnershipPolicy && this.activeSingleOrganizationPolicy) { } else if (!this.activeOrganizationDataOwnership && this.activeSingleOrganizationPolicy) {
displayMode = "singleOrganizationPolicy"; displayMode = "singleOrganizationPolicy";
} else if (this.activePersonalOwnershipPolicy && this.activeSingleOrganizationPolicy) { } else if (this.activeOrganizationDataOwnership && this.activeSingleOrganizationPolicy) {
displayMode = "singleOrganizationAndPersonalOwnershipPolicies"; displayMode = "singleOrganizationAndOrganizatonDataOwnershipPolicies";
} }
return displayMode; return displayMode;

View File

@@ -32,7 +32,7 @@ export class VaultFilterComponent implements OnInit {
isLoaded = false; isLoaded = false;
collapsedFilterNodes: Set<string>; collapsedFilterNodes: Set<string>;
organizations: Organization[]; organizations: Organization[];
activePersonalOwnershipPolicy: boolean; activeOrganizationDataOwnershipPolicy: boolean;
activeSingleOrganizationPolicy: boolean; activeSingleOrganizationPolicy: boolean;
collections: DynamicTreeNode<CollectionView>; collections: DynamicTreeNode<CollectionView>;
folders$: Observable<DynamicTreeNode<FolderView>>; folders$: Observable<DynamicTreeNode<FolderView>>;
@@ -47,8 +47,8 @@ export class VaultFilterComponent implements OnInit {
this.collapsedFilterNodes = await this.vaultFilterService.buildCollapsedFilterNodes(); this.collapsedFilterNodes = await this.vaultFilterService.buildCollapsedFilterNodes();
this.organizations = await this.vaultFilterService.buildOrganizations(); this.organizations = await this.vaultFilterService.buildOrganizations();
if (this.organizations != null && this.organizations.length > 0) { if (this.organizations != null && this.organizations.length > 0) {
this.activePersonalOwnershipPolicy = this.activeOrganizationDataOwnershipPolicy =
await this.vaultFilterService.checkForPersonalOwnershipPolicy(); await this.vaultFilterService.checkForOrganizationDataOwnershipPolicy();
this.activeSingleOrganizationPolicy = this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy(); await this.vaultFilterService.checkForSingleOrganizationPolicy();
} }
@@ -88,8 +88,8 @@ export class VaultFilterComponent implements OnInit {
async reloadOrganizations() { async reloadOrganizations() {
this.organizations = await this.vaultFilterService.buildOrganizations(); this.organizations = await this.vaultFilterService.buildOrganizations();
this.activePersonalOwnershipPolicy = this.activeOrganizationDataOwnershipPolicy =
await this.vaultFilterService.checkForPersonalOwnershipPolicy(); await this.vaultFilterService.checkForOrganizationDataOwnershipPolicy();
this.activeSingleOrganizationPolicy = this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy(); await this.vaultFilterService.checkForSingleOrganizationPolicy();
} }

View File

@@ -2,5 +2,5 @@ export type DisplayMode =
| "noOrganizations" | "noOrganizations"
| "organizationMember" | "organizationMember"
| "singleOrganizationPolicy" | "singleOrganizationPolicy"
| "personalOwnershipPolicy" | "organizationDataOwnershipPolicy"
| "singleOrganizationAndPersonalOwnershipPolicies"; | "singleOrganizationAndOrganizatonDataOwnershipPolicies";

View File

@@ -123,12 +123,12 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
); );
} }
async checkForPersonalOwnershipPolicy(): Promise<boolean> { async checkForOrganizationDataOwnershipPolicy(): Promise<boolean> {
return await firstValueFrom( return await firstValueFrom(
this.accountService.activeAccount$.pipe( this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
), ),
); );

View File

@@ -6,7 +6,7 @@ export enum PolicyType {
PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases
SingleOrg = 3, // Allows users to only be apart of one organization SingleOrg = 3, // Allows users to only be apart of one organization
RequireSso = 4, // Requires users to authenticate with SSO RequireSso = 4, // Requires users to authenticate with SSO
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items OrganizationDataOwnership = 5, // Enforces organization ownership items added/cloned to the default collection
DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow

View File

@@ -238,8 +238,8 @@ export class DefaultPolicyService implements PolicyService {
case PolicyType.RestrictedItemTypes: case PolicyType.RestrictedItemTypes:
// restricted item types policy // restricted item types policy
return false; return false;
case PolicyType.PersonalOwnership: case PolicyType.OrganizationDataOwnership:
// individual vault policy applies to everyone except admins and owners // organization data ownership policy applies to everyone except admins and owners
return organization.isAdmin; return organization.isAdmin;
default: default:
return organization.canManagePolicies; return organization.canManagePolicies;

View File

@@ -336,7 +336,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
this.accountService.activeAccount$.pipe( this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
), ),
this.organizations$, this.organizations$,

View File

@@ -19,7 +19,7 @@
[label]="'myVault' | i18n" [label]="'myVault' | i18n"
value="myVault" value="myVault"
icon="bwi-user" icon="bwi-user"
*ngIf="!(disablePersonalOwnershipPolicy$ | async)" *ngIf="!(organizationDataOwnershipPolicy$ | async)"
/> />
<bit-option <bit-option
*ngFor="let o of organizations$ | async" *ngFor="let o of organizations$ | async"

View File

@@ -153,7 +153,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
} }
disablePersonalVaultExportPolicy$: Observable<boolean>; disablePersonalVaultExportPolicy$: Observable<boolean>;
disablePersonalOwnershipPolicy$: Observable<boolean>; organizationDataOwnershipPolicy$: Observable<boolean>;
exportForm = this.formBuilder.group({ exportForm = this.formBuilder.group({
vaultSelector: [ vaultSelector: [
@@ -209,10 +209,10 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
), ),
); );
this.disablePersonalOwnershipPolicy$ = this.accountService.activeAccount$.pipe( this.organizationDataOwnershipPolicy$ = this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
); );
@@ -294,21 +294,21 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
combineLatest([ combineLatest([
this.disablePersonalVaultExportPolicy$, this.disablePersonalVaultExportPolicy$,
this.disablePersonalOwnershipPolicy$, this.organizationDataOwnershipPolicy$,
this.organizations$, this.organizations$,
]) ])
.pipe( .pipe(
tap(([disablePersonalVaultExport, disablePersonalOwnership, organizations]) => { tap(([disablePersonalVaultExport, organizationDataOwnership, organizations]) => {
this._disabledByPolicy = disablePersonalVaultExport; this._disabledByPolicy = disablePersonalVaultExport;
// When personalOwnership is disabled and we have orgs, set the first org as the selected vault // When organizationDataOwnership is enabled and we have orgs, set the first org as the selected vault
if (disablePersonalOwnership && organizations.length > 0) { if (organizationDataOwnership && organizations.length > 0) {
this.exportForm.enable(); this.exportForm.enable();
this.exportForm.controls.vaultSelector.setValue(organizations[0].id); this.exportForm.controls.vaultSelector.setValue(organizations[0].id);
} }
// When personalOwnership is disabled and we have no orgs, disable the form // When organizationDataOwnership is enabled and we have no orgs, disable the form
if (disablePersonalOwnership && organizations.length === 0) { if (organizationDataOwnership && organizations.length === 0) {
this.exportForm.disable(); this.exportForm.disable();
} }
@@ -318,7 +318,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
} }
// When neither policy is enabled, enable the form and set the default vault to "myVault" // When neither policy is enabled, enable the form and set the default vault to "myVault"
if (!disablePersonalVaultExport && !disablePersonalOwnership) { if (!disablePersonalVaultExport && !organizationDataOwnership) {
this.exportForm.controls.vaultSelector.setValue("myVault"); this.exportForm.controls.vaultSelector.setValue("myVault");
} }
}), }),

View File

@@ -77,7 +77,7 @@ type BaseCipherFormConfig = {
* Flag to indicate if the user is allowed to create ciphers in their own Vault. If false, configuration must * Flag to indicate if the user is allowed to create ciphers in their own Vault. If false, configuration must
* supply a list of organizations that the user can create ciphers in. * supply a list of organizations that the user can create ciphers in.
*/ */
allowPersonalOwnership: boolean; organizationDataOwnershipDisabled: boolean;
/** /**
* The original cipher that is being edited or cloned. This can be undefined when creating a new cipher. * The original cipher that is being edited or cloned. This can be undefined when creating a new cipher.
@@ -131,18 +131,18 @@ type CreateNewCipherConfig = BaseCipherFormConfig & {
type CombinedAddEditConfig = ExistingCipherConfig | CreateNewCipherConfig; type CombinedAddEditConfig = ExistingCipherConfig | CreateNewCipherConfig;
/** /**
* Configuration object for the cipher form when personal ownership is allowed. * Configuration object for the cipher form when organization data ownership is not allowed.
*/ */
type PersonalOwnershipAllowed = CombinedAddEditConfig & { type OrganizationDataOwnershipDisabled = CombinedAddEditConfig & {
allowPersonalOwnership: true; organizationDataOwnershipDisabled: true;
}; };
/** /**
* Configuration object for the cipher form when personal ownership is not allowed. * Configuration object for the cipher form when organization data ownership is allowed.
* Organizations must be provided. * Organizations must be provided.
*/ */
type PersonalOwnershipNotAllowed = CombinedAddEditConfig & { type OrganizationDataOwnershipEnabled = CombinedAddEditConfig & {
allowPersonalOwnership: false; organizationDataOwnershipDisabled: false;
organizations: Organization[]; organizations: Organization[];
}; };
@@ -150,7 +150,7 @@ type PersonalOwnershipNotAllowed = CombinedAddEditConfig & {
* Configuration object for the cipher form. * Configuration object for the cipher form.
* Determines the behavior of the form and the controls that are displayed/enabled. * Determines the behavior of the form and the controls that are displayed/enabled.
*/ */
export type CipherFormConfig = PersonalOwnershipAllowed | PersonalOwnershipNotAllowed; export type CipherFormConfig = OrganizationDataOwnershipDisabled | OrganizationDataOwnershipEnabled;
/** /**
* Service responsible for building the configuration object for the cipher form. * Service responsible for building the configuration object for the cipher form.

View File

@@ -57,7 +57,7 @@ const defaultConfig: CipherFormConfig = {
mode: "add", mode: "add",
cipherType: CipherType.Login, cipherType: CipherType.Login,
admin: false, admin: false,
allowPersonalOwnership: true, organizationDataOwnershipDisabled: true,
collections: [ collections: [
{ {
id: "col1", id: "col1",
@@ -354,13 +354,13 @@ export const WithSubmitButton: Story = {
}, },
}; };
export const NoPersonalOwnership: Story = { export const OrganizationDataOwnershipEnabled: Story = {
...Add, ...Add,
args: { args: {
config: { config: {
...defaultConfig, ...defaultConfig,
mode: "add", mode: "add",
allowPersonalOwnership: false, organizationDataOwnershipDisabled: false,
originalCipher: defaultConfig.originalCipher, originalCipher: defaultConfig.originalCipher,
organizations: defaultConfig.organizations!, organizations: defaultConfig.organizations!,
}, },

View File

@@ -22,7 +22,7 @@
<bit-label>{{ "owner" | i18n }}</bit-label> <bit-label>{{ "owner" | i18n }}</bit-label>
<bit-select formControlName="organizationId"> <bit-select formControlName="organizationId">
<bit-option <bit-option
*ngIf="showPersonalOwnerOption" *ngIf="showOrganizationDataOwnershipOption"
[value]="null" [value]="null"
[label]="userEmail$ | async" [label]="userEmail$ | async"
></bit-option> ></bit-option>

View File

@@ -93,8 +93,8 @@ describe("ItemDetailsSectionComponent", () => {
}); });
describe("ngOnInit", () => { describe("ngOnInit", () => {
it("should throw an error if no organizations are available for ownership and personal ownership is not allowed", async () => { it("should throw an error if no organizations are available for ownership and organization data ownership is enabled", async () => {
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.config.organizations = []; component.config.organizations = [];
await expect(component.ngOnInit()).rejects.toThrow( await expect(component.ngOnInit()).rejects.toThrow(
"No organizations available for ownership.", "No organizations available for ownership.",
@@ -102,7 +102,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should initialize form with default values if no originalCipher is provided", fakeAsync(async () => { it("should initialize form with default values if no originalCipher is provided", fakeAsync(async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
await component.ngOnInit(); await component.ngOnInit();
tick(); tick();
@@ -120,7 +120,7 @@ describe("ItemDetailsSectionComponent", () => {
})); }));
it("should initialize form with values from originalCipher if provided", fakeAsync(async () => { it("should initialize form with values from originalCipher if provided", fakeAsync(async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
component.config.collections = [ component.config.collections = [
createMockCollection("col1", "Collection 1", "org1") as CollectionView, createMockCollection("col1", "Collection 1", "org1") as CollectionView,
@@ -150,7 +150,7 @@ describe("ItemDetailsSectionComponent", () => {
})); }));
it("should disable organizationId control if ownership change is not allowed", async () => { it("should disable organizationId control if ownership change is not allowed", async () => {
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(false); jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(false);
@@ -188,15 +188,15 @@ describe("ItemDetailsSectionComponent", () => {
expect(component.allowOwnershipChange).toBe(false); expect(component.allowOwnershipChange).toBe(false);
}); });
it("should allow ownership change if personal ownership is allowed and there is at least one organization", () => { it("should allow ownership change if organization data ownership is disabled and there is at least one organization", () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1", name: "org1" } as Organization]; component.config.organizations = [{ id: "org1", name: "org1" } as Organization];
fixture.detectChanges(); fixture.detectChanges();
expect(component.allowOwnershipChange).toBe(true); expect(component.allowOwnershipChange).toBe(true);
}); });
it("should allow ownership change if personal ownership is not allowed but there is more than one organization", () => { it("should allow ownership change if organization data ownership is enabled but there is more than one organization", () => {
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.config.organizations = [ component.config.organizations = [
{ id: "org1", name: "org1" } as Organization, { id: "org1", name: "org1" } as Organization,
{ id: "org2", name: "org2" } as Organization, { id: "org2", name: "org2" } as Organization,
@@ -207,23 +207,23 @@ describe("ItemDetailsSectionComponent", () => {
}); });
describe("defaultOwner", () => { describe("defaultOwner", () => {
it("should return null if personal ownership is allowed", () => { it("should return null if organization data ownership is disabled", () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
expect(component.defaultOwner).toBeNull(); expect(component.defaultOwner).toBeNull();
}); });
it("should return the first organization id if personal ownership is not allowed", () => { it("should return the first organization id if organization data ownership is enabled", () => {
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.config.organizations = [{ id: "org1", name: "Organization 1" } as Organization]; component.config.organizations = [{ id: "org1", name: "Organization 1" } as Organization];
fixture.detectChanges(); fixture.detectChanges();
expect(component.defaultOwner).toBe("org1"); expect(component.defaultOwner).toBe("org1");
}); });
}); });
describe("showPersonalOwnerOption", () => { describe("showOrganizationDataOwnershipOption", () => {
it("should show personal ownership when the configuration allows", () => { it("should show organization data ownership when the configuration allows", () => {
component.config.mode = "edit"; component.config.mode = "edit";
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.originalCipherView = {} as CipherView; component.originalCipherView = {} as CipherView;
component.config.organizations = [{ id: "134-433-22" } as Organization]; component.config.organizations = [{ id: "134-433-22" } as Organization];
fixture.detectChanges(); fixture.detectChanges();
@@ -235,9 +235,9 @@ describe("ItemDetailsSectionComponent", () => {
expect(label).toBe("test@example.com"); expect(label).toBe("test@example.com");
}); });
it("should show personal ownership when the control is disabled", async () => { it("should show organization data ownership when the control is disabled", async () => {
component.config.mode = "edit"; component.config.mode = "edit";
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.originalCipherView = {} as CipherView; component.originalCipherView = {} as CipherView;
component.config.organizations = [{ id: "134-433-22" } as Organization]; component.config.organizations = [{ id: "134-433-22" } as Organization];
await component.ngOnInit(); await component.ngOnInit();
@@ -253,7 +253,7 @@ describe("ItemDetailsSectionComponent", () => {
describe("showOwnership", () => { describe("showOwnership", () => {
it("should return true if ownership change is allowed or in edit mode with at least one organization", () => { it("should return true if ownership change is allowed or in edit mode with at least one organization", () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true); jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true);
expect(component.showOwnership).toBe(true); expect(component.showOwnership).toBe(true);
@@ -265,7 +265,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should hide the ownership control if showOwnership is false", async () => { it("should hide the ownership control if showOwnership is false", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
jest.spyOn(component, "showOwnership", "get").mockReturnValue(false); jest.spyOn(component, "showOwnership", "get").mockReturnValue(false);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@@ -276,7 +276,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should show the ownership control if showOwnership is true", async () => { it("should show the ownership control if showOwnership is true", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true); jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@@ -293,7 +293,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should append '- Clone' to the title if in clone mode", async () => { it("should append '- Clone' to the title if in clone mode", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
const cipher = { const cipher = {
name: "cipher1", name: "cipher1",
organizationId: null, organizationId: null,
@@ -312,7 +312,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("does not append clone when the cipher was populated from the cache", async () => { it("does not append clone when the cipher was populated from the cache", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
const cipher = { const cipher = {
name: "from cache cipher", name: "from cache cipher",
organizationId: null, organizationId: null,
@@ -332,8 +332,8 @@ describe("ItemDetailsSectionComponent", () => {
expect(component.itemDetailsForm.controls.name.value).toBe("from cache cipher"); expect(component.itemDetailsForm.controls.name.value).toBe("from cache cipher");
}); });
it("should select the first organization if personal ownership is not allowed", async () => { it("should select the first organization if organization data ownership is enabled", async () => {
component.config.allowPersonalOwnership = false; component.config.organizationDataOwnershipDisabled = false;
component.config.organizations = [ component.config.organizations = [
{ id: "org1", name: "org1" } as Organization, { id: "org1", name: "org1" } as Organization,
{ id: "org2", name: "org2" } as Organization, { id: "org2", name: "org2" } as Organization,
@@ -354,7 +354,7 @@ describe("ItemDetailsSectionComponent", () => {
describe("collectionOptions", () => { describe("collectionOptions", () => {
it("should reset and disable/hide collections control when no organization is selected", async () => { it("should reset and disable/hide collections control when no organization is selected", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.itemDetailsForm.controls.organizationId.setValue(null); component.itemDetailsForm.controls.organizationId.setValue(null);
fixture.detectChanges(); fixture.detectChanges();
@@ -370,7 +370,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should enable/show collection control when an organization is selected", async () => { it("should enable/show collection control when an organization is selected", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
component.config.collections = [ component.config.collections = [
createMockCollection("col1", "Collection 1", "org1") as CollectionView, createMockCollection("col1", "Collection 1", "org1") as CollectionView,
@@ -421,7 +421,7 @@ describe("ItemDetailsSectionComponent", () => {
}); });
it("should automatically select the first collection if only one is available", async () => { it("should automatically select the first collection if only one is available", async () => {
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
component.config.collections = [ component.config.collections = [
createMockCollection("col1", "Collection 1", "org1") as CollectionView, createMockCollection("col1", "Collection 1", "org1") as CollectionView,
@@ -475,7 +475,7 @@ describe("ItemDetailsSectionComponent", () => {
it("should allow all collections to be altered when `config.admin` is true", async () => { it("should allow all collections to be altered when `config.admin` is true", async () => {
component.config.admin = true; component.config.admin = true;
component.config.allowPersonalOwnership = true; component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization]; component.config.organizations = [{ id: "org1" } as Organization];
component.config.collections = [ component.config.collections = [
createMockCollection("col1", "Collection 1", "org1", true, false) as CollectionView, createMockCollection("col1", "Collection 1", "org1", true, false) as CollectionView,
@@ -491,7 +491,6 @@ describe("ItemDetailsSectionComponent", () => {
expect(component["collectionOptions"].map((c) => c.id)).toEqual(["col1", "col2", "col3"]); expect(component["collectionOptions"].map((c) => c.id)).toEqual(["col1", "col2", "col3"]);
}); });
}); });
describe("readonlyCollections", () => { describe("readonlyCollections", () => {
beforeEach(() => { beforeEach(() => {
component.config.mode = "edit"; component.config.mode = "edit";

View File

@@ -92,8 +92,8 @@ export class ItemDetailsSectionComponent implements OnInit {
return this.config.mode === "partial-edit"; return this.config.mode === "partial-edit";
} }
get allowPersonalOwnership() { get organizationDataOwnershipDisabled() {
return this.config.allowPersonalOwnership; return this.config.organizationDataOwnershipDisabled;
} }
get collections(): CollectionView[] { get collections(): CollectionView[] {
@@ -105,14 +105,17 @@ export class ItemDetailsSectionComponent implements OnInit {
} }
/** /**
* Show the personal ownership option in the Owner dropdown when: * Show the organization data ownership option in the Owner dropdown when:
* - Personal ownership is allowed * - organization data ownership is disabled
* - The `organizationId` control is disabled. This avoids the scenario * - The `organizationId` control is disabled. This avoids the scenario
* where a the dropdown is empty because the user personally owns the cipher * where a the dropdown is empty because the user personally owns the cipher
* but cannot edit the ownership. * but cannot edit the ownership.
*/ */
get showPersonalOwnerOption() { get showOrganizationDataOwnershipOption() {
return this.allowPersonalOwnership || !this.itemDetailsForm.controls.organizationId.enabled; return (
this.organizationDataOwnershipDisabled ||
!this.itemDetailsForm.controls.organizationId.enabled
);
} }
constructor( constructor(
@@ -161,7 +164,7 @@ export class ItemDetailsSectionComponent implements OnInit {
} }
// If personal ownership is allowed and there is at least one organization, allow ownership change. // If personal ownership is allowed and there is at least one organization, allow ownership change.
if (this.allowPersonalOwnership) { if (this.organizationDataOwnershipDisabled) {
return this.organizations.length > 0; return this.organizations.length > 0;
} }
@@ -180,7 +183,7 @@ export class ItemDetailsSectionComponent implements OnInit {
} }
get defaultOwner() { get defaultOwner() {
return this.allowPersonalOwnership ? null : this.organizations[0].id; return this.organizationDataOwnershipDisabled ? null : this.organizations[0].id;
} }
async ngOnInit() { async ngOnInit() {
@@ -188,7 +191,7 @@ export class ItemDetailsSectionComponent implements OnInit {
Utils.getSortFunction(this.i18nService, "name"), Utils.getSortFunction(this.i18nService, "name"),
); );
if (!this.allowPersonalOwnership && this.organizations.length === 0) { if (!this.organizationDataOwnershipDisabled && this.organizations.length === 0) {
throw new Error("No organizations available for ownership."); throw new Error("No organizations available for ownership.");
} }
@@ -244,7 +247,7 @@ export class ItemDetailsSectionComponent implements OnInit {
); );
} }
if (!this.allowPersonalOwnership && prefillCipher.organizationId == null) { if (!this.organizationDataOwnershipDisabled && prefillCipher.organizationId == null) {
this.itemDetailsForm.controls.organizationId.setValue(this.defaultOwner); this.itemDetailsForm.controls.organizationId.setValue(this.defaultOwner);
} }
} }

View File

@@ -44,7 +44,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
): Promise<CipherFormConfig> { ): Promise<CipherFormConfig> {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const [organizations, collections, allowPersonalOwnership, folders, cipher] = const [organizations, collections, organizationDataOwnershipDisabled, folders, cipher] =
await firstValueFrom( await firstValueFrom(
combineLatest([ combineLatest([
this.organizations$(activeUserId), this.organizations$(activeUserId),
@@ -55,7 +55,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
), ),
), ),
), ),
this.allowPersonalOwnership$, this.organizationDataOwnershipDisabled$,
this.folderService.folders$(activeUserId).pipe( this.folderService.folders$(activeUserId).pipe(
switchMap((f) => switchMap((f) =>
this.folderService.folderViews$(activeUserId).pipe( this.folderService.folderViews$(activeUserId).pipe(
@@ -71,7 +71,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
mode, mode,
cipherType: cipher?.type ?? cipherType ?? CipherType.Login, cipherType: cipher?.type ?? cipherType ?? CipherType.Login,
admin: false, admin: false,
allowPersonalOwnership, organizationDataOwnershipDisabled,
originalCipher: cipher, originalCipher: cipher,
collections, collections,
organizations, organizations,
@@ -91,10 +91,10 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
); );
} }
private allowPersonalOwnership$ = this.accountService.activeAccount$.pipe( private organizationDataOwnershipDisabled$ = this.accountService.activeAccount$.pipe(
getUserId, getUserId,
switchMap((userId) => switchMap((userId) =>
this.policyService.policyAppliesToUser$(PolicyType.PersonalOwnership, userId), this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
), ),
map((p) => !p), map((p) => !p),
); );