mirror of
https://github.com/bitwarden/browser
synced 2026-03-02 19:41:26 +00:00
Merge branch 'main' into auth/pm-22723/policy-service-updates
This commit is contained in:
@@ -228,16 +228,6 @@ export abstract class ApiService {
|
||||
request: CipherBulkRestoreRequest,
|
||||
) => Promise<ListResponse<CipherResponse>>;
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
postCipherAttachmentLegacy: (id: string, data: FormData) => Promise<CipherResponse>;
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
postCipherAttachmentAdminLegacy: (id: string, data: FormData) => Promise<CipherResponse>;
|
||||
postCipherAttachment: (
|
||||
id: string,
|
||||
request: AttachmentRequest,
|
||||
|
||||
@@ -92,6 +92,27 @@ describe("FidoAuthenticatorService", () => {
|
||||
});
|
||||
|
||||
describe("createCredential", () => {
|
||||
describe("Mapping params should handle variations in input formats", () => {
|
||||
it.each([
|
||||
[true, true],
|
||||
[false, false],
|
||||
["false", false],
|
||||
["", false],
|
||||
["true", true],
|
||||
])("requireResidentKey should handle %s as boolean %s", async (input, expected) => {
|
||||
const params = createParams({
|
||||
authenticatorSelection: { requireResidentKey: input as any },
|
||||
extensions: { credProps: true },
|
||||
});
|
||||
|
||||
authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult());
|
||||
|
||||
const result = await client.createCredential(params, windowReference);
|
||||
|
||||
expect(result.extensions.credProps?.rk).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("input parameters validation", () => {
|
||||
// Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException.
|
||||
it("should throw error if sameOriginWithAncestors is false", async () => {
|
||||
|
||||
@@ -483,11 +483,15 @@ function mapToMakeCredentialParams({
|
||||
type: credential.type,
|
||||
})) ?? [];
|
||||
|
||||
/**
|
||||
* Quirk: Accounts for the fact that some RP's mistakenly submits 'requireResidentKey' as a string
|
||||
*/
|
||||
const requireResidentKey =
|
||||
params.authenticatorSelection?.residentKey === "required" ||
|
||||
params.authenticatorSelection?.residentKey === "preferred" ||
|
||||
(params.authenticatorSelection?.residentKey === undefined &&
|
||||
params.authenticatorSelection?.requireResidentKey === true);
|
||||
(params.authenticatorSelection?.requireResidentKey === true ||
|
||||
(params.authenticatorSelection?.requireResidentKey as unknown as string) === "true"));
|
||||
|
||||
const requireUserVerification =
|
||||
params.authenticatorSelection?.userVerification === "required" ||
|
||||
|
||||
@@ -1,6 +1,45 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
import type {
|
||||
AssertCredentialResult,
|
||||
CreateCredentialResult,
|
||||
} from "../../abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
// @ts-strict-ignore
|
||||
export class Fido2Utils {
|
||||
static createResultToJson(result: CreateCredentialResult): any {
|
||||
return {
|
||||
id: result.credentialId,
|
||||
rawId: result.credentialId,
|
||||
response: {
|
||||
clientDataJSON: result.clientDataJSON,
|
||||
authenticatorData: result.authData,
|
||||
transports: result.transports,
|
||||
publicKey: result.publicKey,
|
||||
publicKeyAlgorithm: result.publicKeyAlgorithm,
|
||||
attestationObject: result.attestationObject,
|
||||
},
|
||||
authenticatorAttachment: "platform",
|
||||
clientExtensionResults: result.extensions,
|
||||
type: "public-key",
|
||||
};
|
||||
}
|
||||
|
||||
static getResultToJson(result: AssertCredentialResult): any {
|
||||
return {
|
||||
id: result.credentialId,
|
||||
rawId: result.credentialId,
|
||||
response: {
|
||||
clientDataJSON: result.clientDataJSON,
|
||||
authenticatorData: result.authenticatorData,
|
||||
signature: result.signature,
|
||||
userHandle: result.userHandle,
|
||||
},
|
||||
authenticatorAttachment: "platform",
|
||||
clientExtensionResults: {},
|
||||
type: "public-key",
|
||||
};
|
||||
}
|
||||
|
||||
static bufferToString(bufferSource: BufferSource): string {
|
||||
return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource))
|
||||
.replace(/\+/g, "-")
|
||||
|
||||
@@ -212,6 +212,7 @@ export class DefaultSdkService implements SdkService {
|
||||
},
|
||||
},
|
||||
privateKey,
|
||||
signingKey: undefined,
|
||||
});
|
||||
|
||||
// We initialize the org crypto even if the org_keys are
|
||||
|
||||
@@ -639,24 +639,6 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new AttachmentUploadDataResponse(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
async postCipherAttachmentLegacy(id: string, data: FormData): Promise<CipherResponse> {
|
||||
const r = await this.send("POST", "/ciphers/" + id + "/attachment", data, true, true);
|
||||
return new CipherResponse(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
async postCipherAttachmentAdminLegacy(id: string, data: FormData): Promise<CipherResponse> {
|
||||
const r = await this.send("POST", "/ciphers/" + id + "/attachment-admin", data, true, true);
|
||||
return new CipherResponse(r);
|
||||
}
|
||||
|
||||
deleteCipherAttachment(id: string, attachmentId: string): Promise<any> {
|
||||
return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, true);
|
||||
}
|
||||
|
||||
@@ -157,6 +157,15 @@ export class CardView extends ItemView {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.assign(new CardView(), obj);
|
||||
const cardView = new CardView();
|
||||
|
||||
cardView.cardholderName = obj.cardholderName ?? null;
|
||||
cardView.brand = obj.brand ?? null;
|
||||
cardView.number = obj.number ?? null;
|
||||
cardView.expMonth = obj.expMonth ?? null;
|
||||
cardView.expYear = obj.expYear ?? null;
|
||||
cardView.code = obj.code ?? null;
|
||||
|
||||
return cardView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,27 @@ export class IdentityView extends ItemView {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.assign(new IdentityView(), obj);
|
||||
const identityView = new IdentityView();
|
||||
|
||||
identityView.title = obj.title ?? null;
|
||||
identityView.firstName = obj.firstName ?? null;
|
||||
identityView.middleName = obj.middleName ?? null;
|
||||
identityView.lastName = obj.lastName ?? null;
|
||||
identityView.address1 = obj.address1 ?? null;
|
||||
identityView.address2 = obj.address2 ?? null;
|
||||
identityView.address3 = obj.address3 ?? null;
|
||||
identityView.city = obj.city ?? null;
|
||||
identityView.state = obj.state ?? null;
|
||||
identityView.postalCode = obj.postalCode ?? null;
|
||||
identityView.country = obj.country ?? null;
|
||||
identityView.company = obj.company ?? null;
|
||||
identityView.email = obj.email ?? null;
|
||||
identityView.phone = obj.phone ?? null;
|
||||
identityView.ssn = obj.ssn ?? null;
|
||||
identityView.username = obj.username ?? null;
|
||||
identityView.passportNumber = obj.passportNumber ?? null;
|
||||
identityView.licenseNumber = obj.licenseNumber ?? null;
|
||||
|
||||
return identityView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,13 +116,18 @@ export class LoginView extends ItemView {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const passwordRevisionDate =
|
||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||
const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || [];
|
||||
const loginView = new LoginView();
|
||||
|
||||
return Object.assign(new LoginView(), obj, {
|
||||
passwordRevisionDate,
|
||||
uris,
|
||||
});
|
||||
loginView.username = obj.username ?? null;
|
||||
loginView.password = obj.password ?? null;
|
||||
loginView.passwordRevisionDate =
|
||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||
loginView.totp = obj.totp ?? null;
|
||||
loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null;
|
||||
loginView.uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || [];
|
||||
// FIDO2 credentials are not decrypted here, they remain encrypted
|
||||
loginView.fido2Credentials = null;
|
||||
|
||||
return loginView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ export class SecureNoteView extends ItemView {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.assign(new SecureNoteView(), obj);
|
||||
const secureNoteView = new SecureNoteView();
|
||||
secureNoteView.type = obj.type ?? null;
|
||||
|
||||
return secureNoteView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,12 @@ export class SshKeyView extends ItemView {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keyFingerprint = obj.fingerprint;
|
||||
const sshKeyView = new SshKeyView();
|
||||
|
||||
return Object.assign(new SshKeyView(), obj, {
|
||||
keyFingerprint,
|
||||
});
|
||||
sshKeyView.privateKey = obj.privateKey ?? null;
|
||||
sshKeyView.publicKey = obj.publicKey ?? null;
|
||||
sshKeyView.keyFingerprint = obj.fingerprint ?? null;
|
||||
|
||||
return sshKeyView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { ITreeNodeObject, TreeNode } from "./models/domain/tree-node";
|
||||
|
||||
export class ServiceUtils {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
FileUploadApiMethods,
|
||||
FileUploadService,
|
||||
} from "../../../platform/abstractions/file-upload/file-upload.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
@@ -47,18 +46,7 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti
|
||||
this.generateMethods(uploadDataResponse, response, request.adminRequest),
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
(e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) ||
|
||||
(e as ErrorResponse).statusCode === 405
|
||||
) {
|
||||
response = await this.legacyServerAttachmentFileUpload(
|
||||
request.adminRequest,
|
||||
cipher.id,
|
||||
encFileName,
|
||||
encData,
|
||||
dataEncKey[1],
|
||||
);
|
||||
} else if (e instanceof ErrorResponse) {
|
||||
if (e instanceof ErrorResponse) {
|
||||
throw new Error((e as ErrorResponse).getSingleMessage());
|
||||
} else {
|
||||
throw e;
|
||||
@@ -113,50 +101,4 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
async legacyServerAttachmentFileUpload(
|
||||
admin: boolean,
|
||||
cipherId: string,
|
||||
encFileName: EncString,
|
||||
encData: EncArrayBuffer,
|
||||
key: EncString,
|
||||
) {
|
||||
const fd = new FormData();
|
||||
try {
|
||||
const blob = new Blob([encData.buffer], { type: "application/octet-stream" });
|
||||
fd.append("key", key.encryptedString);
|
||||
fd.append("data", blob, encFileName.encryptedString);
|
||||
} catch (e) {
|
||||
if (Utils.isNode && !Utils.isBrowser) {
|
||||
fd.append("key", key.encryptedString);
|
||||
fd.append(
|
||||
"data",
|
||||
Buffer.from(encData.buffer) as any,
|
||||
{
|
||||
filepath: encFileName.encryptedString,
|
||||
contentType: "application/octet-stream",
|
||||
} as any,
|
||||
);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let response: CipherResponse;
|
||||
try {
|
||||
if (admin) {
|
||||
response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd);
|
||||
} else {
|
||||
response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error((e as ErrorResponse).getSingleMessage());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { RestrictedItemTypesService, RestrictedCipherType } from "./restricted-item-types.service";
|
||||
|
||||
describe("RestrictedItemTypesService", () => {
|
||||
let service: RestrictedItemTypesService;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
let fakeAccount: Account | null;
|
||||
|
||||
const org1: Organization = { id: "org1" } as any;
|
||||
const org2: Organization = { id: "org2" } as any;
|
||||
|
||||
const policyOrg1 = {
|
||||
organizationId: "org1",
|
||||
type: PolicyType.RestrictedItemTypes,
|
||||
enabled: true,
|
||||
data: [CipherType.Card],
|
||||
} as Policy;
|
||||
|
||||
const policyOrg2 = {
|
||||
organizationId: "org2",
|
||||
type: PolicyType.RestrictedItemTypes,
|
||||
enabled: true,
|
||||
data: [CipherType.Card],
|
||||
} as Policy;
|
||||
|
||||
beforeEach(() => {
|
||||
policyService = mock<PolicyService>();
|
||||
organizationService = mock<OrganizationService>();
|
||||
accountService = mock<AccountService>();
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
fakeAccount = { id: Utils.newGuid() as UserId } as Account;
|
||||
accountService.activeAccount$ = of(fakeAccount);
|
||||
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
organizationService.organizations$.mockReturnValue(of([org1, org2]));
|
||||
policyService.policiesByType$.mockReturnValue(of([]));
|
||||
|
||||
service = new RestrictedItemTypesService(
|
||||
configService,
|
||||
accountService,
|
||||
organizationService,
|
||||
policyService,
|
||||
);
|
||||
});
|
||||
|
||||
it("emits empty array when feature flag is disabled", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("emits empty array if no organizations exist", async () => {
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
policyService.policiesByType$.mockReturnValue(of([]));
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("defaults undefined data to [Card] and returns empty allowViewOrgIds", async () => {
|
||||
organizationService.organizations$.mockReturnValue(of([org1]));
|
||||
|
||||
const policyForOrg1 = {
|
||||
organizationId: "org1",
|
||||
type: PolicyType.RestrictedItemTypes,
|
||||
enabled: true,
|
||||
data: undefined,
|
||||
} as Policy;
|
||||
policyService.policiesByType$.mockReturnValue(of([policyForOrg1]));
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
expect(result).toEqual<RestrictedCipherType[]>([
|
||||
{ cipherType: CipherType.Card, allowViewOrgIds: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("if one org restricts Card and another has no policy, allowViewOrgIds contains the unrestricted org", async () => {
|
||||
policyService.policiesByType$.mockReturnValue(of([policyOrg1]));
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
expect(result).toEqual<RestrictedCipherType[]>([
|
||||
{ cipherType: CipherType.Card, allowViewOrgIds: ["org2"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty allowViewOrgIds when all orgs restrict the same type", async () => {
|
||||
organizationService.organizations$.mockReturnValue(of([org1, org2]));
|
||||
policyService.policiesByType$.mockReturnValue(of([policyOrg1, policyOrg2]));
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
expect(result).toEqual<RestrictedCipherType[]>([
|
||||
{ cipherType: CipherType.Card, allowViewOrgIds: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("aggregates multiple types and computes allowViewOrgIds correctly", async () => {
|
||||
organizationService.organizations$.mockReturnValue(of([org1, org2]));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([
|
||||
{ ...policyOrg1, data: [CipherType.Card, CipherType.Login] } as Policy,
|
||||
{ ...policyOrg2, data: [CipherType.Card, CipherType.Identity] } as Policy,
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(service.restricted$);
|
||||
|
||||
expect(result).toEqual<RestrictedCipherType[]>([
|
||||
{ cipherType: CipherType.Card, allowViewOrgIds: [] },
|
||||
{ cipherType: CipherType.Login, allowViewOrgIds: ["org2"] },
|
||||
{ cipherType: CipherType.Identity, allowViewOrgIds: ["org1"] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
101
libs/common/src/vault/services/restricted-item-types.service.ts
Normal file
101
libs/common/src/vault/services/restricted-item-types.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { combineLatest, map, of, Observable } from "rxjs";
|
||||
import { switchMap, distinctUntilChanged, shareReplay } from "rxjs/operators";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
export type RestrictedCipherType = {
|
||||
cipherType: CipherType;
|
||||
allowViewOrgIds: string[];
|
||||
};
|
||||
|
||||
export class RestrictedItemTypesService {
|
||||
/**
|
||||
* Emits an array of RestrictedCipherType objects:
|
||||
* - cipherType: each type restricted by at least one org-level policy
|
||||
* - allowViewOrgIds: org IDs that allow viewing that type
|
||||
*/
|
||||
readonly restricted$: Observable<RestrictedCipherType[]> = this.configService
|
||||
.getFeatureFlag$(FeatureFlag.RemoveCardItemTypePolicy)
|
||||
.pipe(
|
||||
switchMap((flagOn) => {
|
||||
if (!flagOn) {
|
||||
return of([]);
|
||||
}
|
||||
return this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) =>
|
||||
combineLatest([
|
||||
this.organizationService.organizations$(userId),
|
||||
this.policyService.policiesByType$(PolicyType.RestrictedItemTypes, userId),
|
||||
]),
|
||||
),
|
||||
map(([orgs, enabledPolicies]) => {
|
||||
// Helper to extract restricted types, defaulting to [Card]
|
||||
const restrictedTypes = (p: (typeof enabledPolicies)[number]) =>
|
||||
(p.data as CipherType[]) ?? [CipherType.Card];
|
||||
|
||||
// Union across all enabled policies
|
||||
const allRestrictedTypes = Array.from(
|
||||
new Set(enabledPolicies.flatMap(restrictedTypes)),
|
||||
);
|
||||
|
||||
return allRestrictedTypes.map((cipherType) => {
|
||||
// Determine which orgs allow viewing this type
|
||||
const allowViewOrgIds = orgs
|
||||
.filter((org) => {
|
||||
const orgPolicy = enabledPolicies.find((p) => p.organizationId === org.id);
|
||||
// no policy for this org => allows everything
|
||||
if (!orgPolicy) {
|
||||
return true;
|
||||
}
|
||||
// if this type not in their restricted list => they allow it
|
||||
return !restrictedTypes(orgPolicy).includes(cipherType);
|
||||
})
|
||||
.map((org) => org.id);
|
||||
|
||||
return { cipherType, allowViewOrgIds };
|
||||
});
|
||||
}),
|
||||
);
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private accountService: AccountService,
|
||||
private organizationService: OrganizationService,
|
||||
private policyService: PolicyService,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that returns whether a cipher is restricted from being viewed by the user
|
||||
* Criteria:
|
||||
* - the cipher's type is restricted by at least one org
|
||||
* UNLESS
|
||||
* - the cipher belongs to an organization and that organization does not restrict that type
|
||||
* OR
|
||||
* - the cipher belongs to the user's personal vault and at least one other organization does not restrict that type
|
||||
*/
|
||||
export function isCipherViewRestricted(
|
||||
cipher: CipherView,
|
||||
restrictedTypes: RestrictedCipherType[],
|
||||
) {
|
||||
return restrictedTypes.some(
|
||||
(restrictedType) =>
|
||||
restrictedType.cipherType === cipher.type &&
|
||||
(cipher.organizationId
|
||||
? !restrictedType.allowViewOrgIds.includes(cipher.organizationId)
|
||||
: restrictedType.allowViewOrgIds.length === 0),
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,6 @@ export const CIPHER_MENU_ITEMS = Object.freeze([
|
||||
{ type: CipherType.Login, icon: "bwi-globe", labelKey: "typeLogin" },
|
||||
{ type: CipherType.Card, icon: "bwi-credit-card", labelKey: "typeCard" },
|
||||
{ type: CipherType.Identity, icon: "bwi-id-card", labelKey: "typeIdentity" },
|
||||
{ type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "note" },
|
||||
{ type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "typeNote" },
|
||||
{ type: CipherType.SshKey, icon: "bwi-key", labelKey: "typeSshKey" },
|
||||
] as const) satisfies readonly CipherMenuItem[];
|
||||
|
||||
Reference in New Issue
Block a user