mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[EC-850] ProviderUser permissions should prevail over member permissions (#5162)
* Apply provider permissions even if also member * Add org.isMember * Refactor: extract syncProfileOrganizations method * Change isNotProvider logic to isMember * Fix cascading org permissions * Add memberOrganizations$ observable
This commit is contained in:
@@ -56,13 +56,22 @@ export function canAccessAdmin(i18nService: I18nService) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isNotProviderUser(org: Organization): boolean {
|
||||
return !org.isProviderUser;
|
||||
/**
|
||||
* Returns `true` if a user is a member of an organization (rather than only being a ProviderUser)
|
||||
* @deprecated Use organizationService.memberOrganizations$ instead
|
||||
*/
|
||||
export function isMember(org: Organization): boolean {
|
||||
return org.isMember;
|
||||
}
|
||||
|
||||
export abstract class OrganizationService {
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
/**
|
||||
* Organizations that the user is a member of (excludes organizations that they only have access to via a provider)
|
||||
*/
|
||||
memberOrganizations$: Observable<Organization[]>;
|
||||
|
||||
get$: (id: string) => Observable<Organization | undefined>;
|
||||
get: (id: string) => Organization;
|
||||
getByIdentifier: (identifier: string) => Organization;
|
||||
|
||||
@@ -38,6 +38,7 @@ export class OrganizationData {
|
||||
providerName: string;
|
||||
providerType?: ProviderType;
|
||||
isProviderUser: boolean;
|
||||
isMember: boolean;
|
||||
familySponsorshipFriendlyName: string;
|
||||
familySponsorshipAvailable: boolean;
|
||||
planProductType: ProductType;
|
||||
@@ -48,7 +49,13 @@ export class OrganizationData {
|
||||
familySponsorshipToDelete?: boolean;
|
||||
accessSecretsManager: boolean;
|
||||
|
||||
constructor(response: ProfileOrganizationResponse) {
|
||||
constructor(
|
||||
response: ProfileOrganizationResponse,
|
||||
options: {
|
||||
isMember: boolean;
|
||||
isProviderUser: boolean;
|
||||
}
|
||||
) {
|
||||
this.id = response.id;
|
||||
this.name = response.name;
|
||||
this.status = response.status;
|
||||
@@ -91,5 +98,8 @@ export class OrganizationData {
|
||||
this.familySponsorshipValidUntil = response.familySponsorshipValidUntil;
|
||||
this.familySponsorshipToDelete = response.familySponsorshipToDelete;
|
||||
this.accessSecretsManager = response.accessSecretsManager;
|
||||
|
||||
this.isMember = options.isMember;
|
||||
this.isProviderUser = options.isProviderUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,14 @@ export class Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
status: OrganizationUserStatusType;
|
||||
|
||||
/**
|
||||
* The member's role in the organization.
|
||||
* Avoid using this for permission checks - use the getters instead (e.g. isOwner, isAdmin, canManageX), because they
|
||||
* properly handle permission inheritance and relationships.
|
||||
*/
|
||||
type: OrganizationUserType;
|
||||
|
||||
enabled: boolean;
|
||||
usePolicies: boolean;
|
||||
useGroups: boolean;
|
||||
@@ -39,7 +46,14 @@ export class Organization {
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
providerType?: ProviderType;
|
||||
/**
|
||||
* Indicates that a user is a ProviderUser for the organization
|
||||
*/
|
||||
isProviderUser: boolean;
|
||||
/**
|
||||
* Indicates that a user is a member for the organization (may be `false` if they have access via a Provider only)
|
||||
*/
|
||||
isMember: boolean;
|
||||
familySponsorshipFriendlyName: string;
|
||||
familySponsorshipAvailable: boolean;
|
||||
planProductType: ProductType;
|
||||
@@ -89,6 +103,7 @@ export class Organization {
|
||||
this.providerName = obj.providerName;
|
||||
this.providerType = obj.providerType;
|
||||
this.isProviderUser = obj.isProviderUser;
|
||||
this.isMember = obj.isMember;
|
||||
this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName;
|
||||
this.familySponsorshipAvailable = obj.familySponsorshipAvailable;
|
||||
this.planProductType = obj.planProductType;
|
||||
@@ -101,24 +116,29 @@ export class Organization {
|
||||
}
|
||||
|
||||
get canAccess() {
|
||||
if (this.type === OrganizationUserType.Owner) {
|
||||
if (this.isOwner) {
|
||||
return true;
|
||||
}
|
||||
return this.enabled && this.status === OrganizationUserStatusType.Confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Manager permissions or greater
|
||||
*/
|
||||
get isManager() {
|
||||
return (
|
||||
this.type === OrganizationUserType.Manager ||
|
||||
this.type === OrganizationUserType.Owner ||
|
||||
this.type === OrganizationUserType.Admin
|
||||
);
|
||||
return this.type === OrganizationUserType.Manager || this.isAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Admin permissions or greater
|
||||
*/
|
||||
get isAdmin() {
|
||||
return this.type === OrganizationUserType.Owner || this.type === OrganizationUserType.Admin;
|
||||
return this.type === OrganizationUserType.Admin || this.isOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Owner permissions (including ProviderUsers)
|
||||
*/
|
||||
get isOwner() {
|
||||
return this.type === OrganizationUserType.Owner || this.isProviderUser;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { BehaviorSubject, concatMap, map, Observable } from "rxjs";
|
||||
|
||||
import { StateService } from "../../../abstractions/state.service";
|
||||
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
InternalOrganizationService as InternalOrganizationServiceAbstraction,
|
||||
isMember,
|
||||
} from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "../../models/data/organization.data";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
|
||||
@@ -9,6 +12,7 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
|
||||
protected _organizations = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
organizations$ = this._organizations.asObservable();
|
||||
memberOrganizations$ = this.organizations$.pipe(map((orgs) => orgs.filter(isMember)));
|
||||
|
||||
constructor(private stateService: StateService) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
|
||||
@@ -260,7 +260,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
await this.stateService.setEncryptedPolicies(null, { userId: userId });
|
||||
}
|
||||
|
||||
private isExcemptFromPolicies(organization: Organization, policyType: PolicyType) {
|
||||
private isExemptFromPolicies(organization: Organization, policyType: PolicyType) {
|
||||
if (policyType === PolicyType.MaximumVaultTimeout) {
|
||||
return organization.type === OrganizationUserType.Owner;
|
||||
}
|
||||
@@ -291,7 +291,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
policySet.has(o.id) &&
|
||||
!this.isExcemptFromPolicies(o, policyType)
|
||||
!this.isExemptFromPolicies(o, policyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user