[AC-1124] Restrict admins from accessing items in the Collections tab (#7537)
* [AC-1124] Add getManyFromApiForOrganization to cipher.service.ts * [AC-1124] Use getManyFromApiForOrganization when a user does not have access to all ciphers * [AC-1124] Vault changes - Show new collection access restricted view - Include unassigned ciphers for restricted admins - Restrict collections when creating/cloning/editing ciphers * [AC-1124] Update edit cipher on page navigation to check if user can access the cipher * [AC-1124] Hide ciphers from restricted collections * [AC-1124] Ensure providers are not shown collection access restricted view * [AC-1124] Modify add-edit component to call the correct endpoint when a restricted admin attempts to add-edit a cipher * [AC-1124] Fix bug after merge with main * [AC-1124] Use private this._organization * [AC-1124] Fix broken builds
This commit is contained in:
parent
3ee27fc61f
commit
5c6245aaae
|
@ -10,6 +10,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -29,7 +30,7 @@ import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils";
|
||||||
import { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service";
|
import { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service";
|
||||||
import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service";
|
import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service";
|
||||||
import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data";
|
import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data";
|
||||||
import { VaultPopoutType, closeAddEditVaultItemPopout } from "../../utils/vault-popout-window";
|
import { closeAddEditVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-vault-add-edit",
|
selector: "app-vault-add-edit",
|
||||||
|
@ -66,6 +67,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
sendApiService: SendApiService,
|
sendApiService: SendApiService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
datePipe: DatePipe,
|
datePipe: DatePipe,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
|
@ -85,6 +87,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
dialogService,
|
dialogService,
|
||||||
window,
|
window,
|
||||||
datePipe,
|
datePipe,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -48,6 +49,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges,
|
||||||
sendApiService: SendApiService,
|
sendApiService: SendApiService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
datePipe: DatePipe,
|
datePipe: DatePipe,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
|
@ -67,6 +69,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges,
|
||||||
dialogService,
|
dialogService,
|
||||||
window,
|
window,
|
||||||
datePipe,
|
datePipe,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -50,6 +51,7 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent {
|
||||||
sendApiService: SendApiService,
|
sendApiService: SendApiService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
datePipe: DatePipe,
|
datePipe: DatePipe,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
|
@ -70,6 +72,7 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent {
|
||||||
sendApiService,
|
sendApiService,
|
||||||
dialogService,
|
dialogService,
|
||||||
datePipe,
|
datePipe,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { EventType, ProductType } from "@bitwarden/common/enums";
|
import { EventType, ProductType } from "@bitwarden/common/enums";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -62,6 +63,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||||
sendApiService: SendApiService,
|
sendApiService: SendApiService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
datePipe: DatePipe,
|
datePipe: DatePipe,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
|
@ -81,6 +83,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||||
dialogService,
|
dialogService,
|
||||||
window,
|
window,
|
||||||
datePipe,
|
datePipe,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -52,6 +53,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
sendApiService: SendApiService,
|
sendApiService: SendApiService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
datePipe: DatePipe,
|
datePipe: DatePipe,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
|
@ -72,6 +74,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
sendApiService,
|
sendApiService,
|
||||||
dialogService,
|
dialogService,
|
||||||
datePipe,
|
datePipe,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +84,9 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
||||||
) {
|
) {
|
||||||
if (this.organization != null) {
|
if (this.organization != null) {
|
||||||
return this.cloneMode && this.organization.canEditAnyCollection;
|
return (
|
||||||
|
this.cloneMode && this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return !this.editMode || this.cloneMode;
|
return !this.editMode || this.cloneMode;
|
||||||
}
|
}
|
||||||
|
@ -90,14 +95,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCollections() {
|
protected loadCollections() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
return super.loadCollections();
|
return super.loadCollections();
|
||||||
}
|
}
|
||||||
return Promise.resolve(this.collections);
|
return Promise.resolve(this.collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
|
@ -110,14 +115,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected encryptCipher() {
|
protected encryptCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
return super.encryptCipher();
|
return super.encryptCipher();
|
||||||
}
|
}
|
||||||
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
|
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteCipher() {
|
protected async deleteCipher() {
|
||||||
if (!this.organization.canEditAnyCollection) {
|
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
return super.deleteCipher();
|
return super.deleteCipher();
|
||||||
}
|
}
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, EventEmitter, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components";
|
import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components";
|
||||||
|
|
||||||
|
@ -13,9 +13,10 @@ const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="140" height=
|
||||||
selector: "collection-access-restricted",
|
selector: "collection-access-restricted",
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, ButtonModule, NoItemsModule],
|
imports: [SharedModule, ButtonModule, NoItemsModule],
|
||||||
template: `<bit-no-items [icon]="icon">
|
template: `<bit-no-items [icon]="icon" class="tw-mt-2 tw-block">
|
||||||
<span slot="title" class="tw-mt-4 tw-block">{{ "collectionAccessRestricted" | i18n }}</span>
|
<span slot="title" class="tw-mt-4 tw-block">{{ "collectionAccessRestricted" | i18n }}</span>
|
||||||
<button
|
<button
|
||||||
|
*ngIf="canEdit"
|
||||||
slot="button"
|
slot="button"
|
||||||
bitButton
|
bitButton
|
||||||
(click)="editInfoClicked.emit()"
|
(click)="editInfoClicked.emit()"
|
||||||
|
@ -29,5 +30,7 @@ const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="140" height=
|
||||||
export class CollectionAccessRestrictedComponent {
|
export class CollectionAccessRestrictedComponent {
|
||||||
protected icon = icon;
|
protected icon = icon;
|
||||||
|
|
||||||
|
@Input() canEdit = false;
|
||||||
|
|
||||||
@Output() editInfoClicked = new EventEmitter<void>();
|
@Output() editInfoClicked = new EventEmitter<void>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "../../individu
|
||||||
import { VaultFilterService } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
import { VaultFilterService } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||||
import {
|
import {
|
||||||
VaultFilterList,
|
VaultFilterList,
|
||||||
VaultFilterType,
|
|
||||||
VaultFilterSection,
|
VaultFilterSection,
|
||||||
|
VaultFilterType,
|
||||||
} from "../../individual-vault/vault-filter/shared/models/vault-filter-section.type";
|
} from "../../individual-vault/vault-filter/shared/models/vault-filter-section.type";
|
||||||
import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type";
|
import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type";
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
[viewingOrgVault]="true"
|
[viewingOrgVault]="true"
|
||||||
>
|
>
|
||||||
</app-vault-items>
|
</app-vault-items>
|
||||||
|
<ng-container *ngIf="!flexibleCollectionsV1Enabled">
|
||||||
<div
|
<div
|
||||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||||
*ngIf="showMissingCollectionPermissionMessage"
|
*ngIf="showMissingCollectionPermissionMessage"
|
||||||
|
@ -85,6 +86,28 @@
|
||||||
{{ "newItem" | i18n }}
|
{{ "newItem" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="flexibleCollectionsV1Enabled && !performingInitialLoad">
|
||||||
|
<bit-no-items *ngIf="isEmpty && !showCollectionAccessRestricted">
|
||||||
|
<span slot="title" class="tw-mt-4 tw-block">{{ "noItemsInList" | i18n }}</span>
|
||||||
|
<button
|
||||||
|
slot="button"
|
||||||
|
bitButton
|
||||||
|
(click)="addCipher()"
|
||||||
|
buttonType="primary"
|
||||||
|
type="button"
|
||||||
|
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" class="bwi bwi-plus"></i> {{ "newItem" | i18n }}
|
||||||
|
</button>
|
||||||
|
</bit-no-items>
|
||||||
|
<collection-access-restricted
|
||||||
|
*ngIf="showCollectionAccessRestricted"
|
||||||
|
[canEdit]="selectedCollection != null && selectedCollection.node.canEdit(organization)"
|
||||||
|
(editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)"
|
||||||
|
>
|
||||||
|
</collection-access-restricted>
|
||||||
|
</ng-container>
|
||||||
<div
|
<div
|
||||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||||
*ngIf="performingInitialLoad"
|
*ngIf="performingInitialLoad"
|
||||||
|
|
|
@ -127,13 +127,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
protected collections: CollectionAdminView[];
|
protected collections: CollectionAdminView[];
|
||||||
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
|
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
|
||||||
protected isEmpty: boolean;
|
protected isEmpty: boolean;
|
||||||
|
/**
|
||||||
|
* Used to show an old missing permission message for custom users with DeleteAnyCollection
|
||||||
|
* @deprecated Replaced with showCollectionAccessRestricted$ and this should be removed after flexible collections V1
|
||||||
|
* is released
|
||||||
|
*/
|
||||||
protected showMissingCollectionPermissionMessage: boolean;
|
protected showMissingCollectionPermissionMessage: boolean;
|
||||||
|
protected showCollectionAccessRestricted: boolean;
|
||||||
protected currentSearchText$: Observable<string>;
|
protected currentSearchText$: Observable<string>;
|
||||||
|
protected editableCollections$: Observable<CollectionView[]>;
|
||||||
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
|
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
|
||||||
FeatureFlag.BulkCollectionAccess,
|
FeatureFlag.BulkCollectionAccess,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
protected flexibleCollectionsEnabled: boolean;
|
protected flexibleCollectionsV1Enabled: boolean;
|
||||||
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
|
@ -176,6 +183,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
: "trashCleanupWarning",
|
: "trashCleanupWarning",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
const filter$ = this.routedVaultFilterService.filter$;
|
const filter$ = this.routedVaultFilterService.filter$;
|
||||||
const organizationId$ = filter$.pipe(
|
const organizationId$ = filter$.pipe(
|
||||||
map((filter) => filter.organizationId),
|
map((filter) => filter.organizationId),
|
||||||
|
@ -259,6 +271,22 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.editableCollections$ = allCollectionsWithoutUnassigned$.pipe(
|
||||||
|
map((collections) => {
|
||||||
|
if (
|
||||||
|
this.organization.canEditAnyCollection &&
|
||||||
|
this.organization.allowAdminAccessToAllCollectionItems
|
||||||
|
) {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
if (this.organization.isProviderUser) {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
return collections.filter((c) => c.assigned && !c.readOnly);
|
||||||
|
}),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
|
);
|
||||||
|
|
||||||
const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe(
|
const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe(
|
||||||
map(([organizationId, allCollections]) => {
|
map(([organizationId, allCollections]) => {
|
||||||
const noneCollection = new CollectionAdminView();
|
const noneCollection = new CollectionAdminView();
|
||||||
|
@ -277,6 +305,18 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
const allCiphers$ = organization$.pipe(
|
const allCiphers$ = organization$.pipe(
|
||||||
concatMap(async (organization) => {
|
concatMap(async (organization) => {
|
||||||
let ciphers;
|
let ciphers;
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
// Flexible collections V1 logic.
|
||||||
|
// If the user can edit all ciphers for the organization then fetch them ALL.
|
||||||
|
if (organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||||
|
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
||||||
|
} else {
|
||||||
|
// Otherwise, only fetch ciphers they have access to (includes unassigned for admins).
|
||||||
|
ciphers = await this.cipherService.getManyFromApiForOrganization(organization.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pre-flexible collections logic, to be removed after flexible collections is fully released
|
||||||
if (organization.canEditAnyCollection) {
|
if (organization.canEditAnyCollection) {
|
||||||
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -284,25 +324,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
(c) => c.organizationId === organization.id,
|
(c) => c.organizationId === organization.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.searchService.indexCiphers(ciphers, organization.id);
|
}
|
||||||
|
|
||||||
|
this.searchService.indexCiphers(ciphers, organization.id);
|
||||||
return ciphers;
|
return ciphers;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const ciphers$ = combineLatest([allCiphers$, filter$, this.currentSearchText$]).pipe(
|
const allCipherMap$ = allCiphers$.pipe(
|
||||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
map((ciphers) => {
|
||||||
concatMap(async ([ciphers, filter, searchText]) => {
|
return Object.fromEntries(ciphers.map((c) => [c.id, c]));
|
||||||
if (filter.collectionId === undefined && filter.type === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterFunction = createFilterFunction(filter);
|
|
||||||
|
|
||||||
if (this.searchService.isSearchable(searchText)) {
|
|
||||||
return await this.searchService.searchCiphers(searchText, [filterFunction], ciphers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ciphers.filter(filterFunction);
|
|
||||||
}),
|
}),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
@ -364,6 +395,52 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showCollectionAccessRestricted$ = combineLatest([
|
||||||
|
filter$,
|
||||||
|
selectedCollection$,
|
||||||
|
organization$,
|
||||||
|
]).pipe(
|
||||||
|
map(([filter, collection, organization]) => {
|
||||||
|
return (
|
||||||
|
(filter.collectionId === Unassigned && !organization.canUseAdminCollections) ||
|
||||||
|
(!organization.allowAdminAccessToAllCollectionItems &&
|
||||||
|
!organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
||||||
|
collection != undefined &&
|
||||||
|
!collection.node.assigned)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ciphers$ = combineLatest([
|
||||||
|
allCiphers$,
|
||||||
|
filter$,
|
||||||
|
this.currentSearchText$,
|
||||||
|
showCollectionAccessRestricted$,
|
||||||
|
]).pipe(
|
||||||
|
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||||
|
concatMap(async ([ciphers, filter, searchText, showCollectionAccessRestricted]) => {
|
||||||
|
if (filter.collectionId === undefined && filter.type === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled && showCollectionAccessRestricted) {
|
||||||
|
// Do not show ciphers for restricted collections
|
||||||
|
// Ciphers belonging to multiple collections may still be present in $allCiphers and shouldn't be visible
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterFunction = createFilterFunction(filter);
|
||||||
|
|
||||||
|
if (this.searchService.isSearchable(searchText)) {
|
||||||
|
return await this.searchService.searchCiphers(searchText, [filterFunction], ciphers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ciphers.filter(filterFunction);
|
||||||
|
}),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
|
);
|
||||||
|
|
||||||
const showMissingCollectionPermissionMessage$ = combineLatest([
|
const showMissingCollectionPermissionMessage$ = combineLatest([
|
||||||
filter$,
|
filter$,
|
||||||
selectedCollection$,
|
selectedCollection$,
|
||||||
|
@ -390,23 +467,28 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
if (!cipherId) {
|
if (!cipherId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
// Handle users with implicit collection access since they use the admin endpoint
|
let canEditCipher: boolean;
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
canEditCipher =
|
||||||
|
organization.canEditAllCiphers(true) ||
|
||||||
|
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
|
||||||
|
} else {
|
||||||
|
canEditCipher =
|
||||||
organization.canUseAdminCollections ||
|
organization.canUseAdminCollections ||
|
||||||
(await this.cipherService.get(cipherId)) != null
|
(await this.cipherService.get(cipherId)) != null;
|
||||||
) {
|
}
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
if (canEditCipher) {
|
||||||
this.editCipherId(cipherId);
|
await this.editCipherId(cipherId);
|
||||||
} else {
|
} else {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
this.i18nService.t("errorOccurred"),
|
this.i18nService.t("errorOccurred"),
|
||||||
this.i18nService.t("unknownCipher"),
|
this.i18nService.t("unknownCipher"),
|
||||||
);
|
);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.router.navigate([], {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.router.navigate([], {
|
|
||||||
queryParams: { cipherId: null, itemId: null },
|
queryParams: { cipherId: null, itemId: null },
|
||||||
queryParamsHandling: "merge",
|
queryParamsHandling: "merge",
|
||||||
});
|
});
|
||||||
|
@ -461,6 +543,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
collections$,
|
collections$,
|
||||||
selectedCollection$,
|
selectedCollection$,
|
||||||
showMissingCollectionPermissionMessage$,
|
showMissingCollectionPermissionMessage$,
|
||||||
|
showCollectionAccessRestricted$,
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
|
@ -475,6 +558,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
collections,
|
collections,
|
||||||
selectedCollection,
|
selectedCollection,
|
||||||
showMissingCollectionPermissionMessage,
|
showMissingCollectionPermissionMessage,
|
||||||
|
showCollectionAccessRestricted,
|
||||||
]) => {
|
]) => {
|
||||||
this.organization = organization;
|
this.organization = organization;
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
@ -484,6 +568,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
this.collections = collections;
|
this.collections = collections;
|
||||||
this.selectedCollection = selectedCollection;
|
this.selectedCollection = selectedCollection;
|
||||||
this.showMissingCollectionPermissionMessage = showMissingCollectionPermissionMessage;
|
this.showMissingCollectionPermissionMessage = showMissingCollectionPermissionMessage;
|
||||||
|
this.showCollectionAccessRestricted = showCollectionAccessRestricted;
|
||||||
|
|
||||||
this.isEmpty = collections?.length === 0 && ciphers?.length === 0;
|
this.isEmpty = collections?.length === 0 && ciphers?.length === 0;
|
||||||
|
|
||||||
// This is a temporary fix to avoid double fetching collections.
|
// This is a temporary fix to avoid double fetching collections.
|
||||||
|
@ -591,13 +677,22 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async editCipherCollections(cipher: CipherView) {
|
async editCipherCollections(cipher: CipherView) {
|
||||||
const currCollections = await firstValueFrom(this.vaultFilterService.filteredCollections$);
|
let collections: CollectionView[] = [];
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
// V1 limits admins to only adding items to collections they have access to.
|
||||||
|
collections = await firstValueFrom(this.editableCollections$);
|
||||||
|
} else {
|
||||||
|
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
||||||
|
(c) => !c.readOnly && c.id != Unassigned,
|
||||||
|
);
|
||||||
|
}
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const [modal] = await this.modalService.openViewRef(
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
this.collectionsModalRef,
|
this.collectionsModalRef,
|
||||||
(comp) => {
|
(comp) => {
|
||||||
comp.collectionIds = cipher.collectionIds;
|
comp.collectionIds = cipher.collectionIds;
|
||||||
comp.collections = currCollections.filter((c) => !c.readOnly && c.id != Unassigned);
|
comp.collections = collections;
|
||||||
comp.organization = this.organization;
|
comp.organization = this.organization;
|
||||||
comp.cipherId = cipher.id;
|
comp.cipherId = cipher.id;
|
||||||
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
comp.onSavedCollections.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||||
|
@ -609,9 +704,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCipher() {
|
async addCipher() {
|
||||||
const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
let collections: CollectionView[] = [];
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
// V1 limits admins to only adding items to collections they have access to.
|
||||||
|
collections = await firstValueFrom(this.editableCollections$);
|
||||||
|
} else {
|
||||||
|
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
||||||
(c) => !c.readOnly && c.id != Unassigned,
|
(c) => !c.readOnly && c.id != Unassigned,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.editCipher(null, (comp) => {
|
await this.editCipher(null, (comp) => {
|
||||||
comp.type = this.activeFilter.cipherType;
|
comp.type = this.activeFilter.cipherType;
|
||||||
|
@ -701,9 +803,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
let collections: CollectionView[] = [];
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
// V1 limits admins to only adding items to collections they have access to.
|
||||||
|
collections = await firstValueFrom(this.editableCollections$);
|
||||||
|
} else {
|
||||||
|
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
||||||
(c) => !c.readOnly && c.id != Unassigned,
|
(c) => !c.readOnly && c.id != Unassigned,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.editCipher(cipher, (comp) => {
|
await this.editCipher(cipher, (comp) => {
|
||||||
comp.cloneMode = true;
|
comp.cloneMode = true;
|
||||||
|
@ -1008,6 +1117,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly CollectionDialogTabType = CollectionDialogTabType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { BreadcrumbsModule } from "@bitwarden/components";
|
import { BreadcrumbsModule, NoItemsModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||||
import { SharedModule } from "../../shared/shared.module";
|
import { SharedModule } from "../../shared/shared.module";
|
||||||
|
@ -31,6 +31,7 @@ import { VaultComponent } from "./vault.component";
|
||||||
VaultItemsModule,
|
VaultItemsModule,
|
||||||
CollectionDialogModule,
|
CollectionDialogModule,
|
||||||
CollectionAccessRestrictedComponent,
|
CollectionAccessRestrictedComponent,
|
||||||
|
NoItemsModule,
|
||||||
],
|
],
|
||||||
declarations: [VaultComponent, VaultHeaderComponent],
|
declarations: [VaultComponent, VaultHeaderComponent],
|
||||||
exports: [VaultComponent],
|
exports: [VaultComponent],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DatePipe } from "@angular/common";
|
import { DatePipe } from "@angular/common";
|
||||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
import { Observable, Subject, takeUntil, concatMap } from "rxjs";
|
import { concatMap, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
@ -12,6 +12,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||||
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -22,7 +24,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { SecureNoteType, UriMatchType, CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType, SecureNoteType, UriMatchType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
|
@ -87,6 +89,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||||
private previousCipherId: string;
|
private previousCipherId: string;
|
||||||
|
|
||||||
|
protected flexibleCollectionsV1Enabled = false;
|
||||||
|
|
||||||
get fido2CredentialCreationDateValue(): string {
|
get fido2CredentialCreationDateValue(): string {
|
||||||
const dateCreated = this.i18nService.t("dateCreated");
|
const dateCreated = this.i18nService.t("dateCreated");
|
||||||
const creationDate = this.datePipe.transform(
|
const creationDate = this.datePipe.transform(
|
||||||
|
@ -114,6 +118,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||||
protected dialogService: DialogService,
|
protected dialogService: DialogService,
|
||||||
protected win: Window,
|
protected win: Window,
|
||||||
protected datePipe: DatePipe,
|
protected datePipe: DatePipe,
|
||||||
|
protected configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
this.typeOptions = [
|
this.typeOptions = [
|
||||||
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
|
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
|
||||||
|
@ -174,6 +179,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
|
false,
|
||||||
|
);
|
||||||
this.writeableCollections = await this.loadCollections();
|
this.writeableCollections = await this.loadCollections();
|
||||||
this.canUseReprompt = await this.passwordRepromptService.enabled();
|
this.canUseReprompt = await this.passwordRepromptService.enabled();
|
||||||
|
|
||||||
|
@ -650,7 +659,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
protected saveCipher(cipher: Cipher) {
|
protected saveCipher(cipher: Cipher) {
|
||||||
const isNotClone = this.editMode && !this.cloneMode;
|
const isNotClone = this.editMode && !this.cloneMode;
|
||||||
const orgAdmin = this.organization?.isAdmin;
|
let orgAdmin = this.organization?.isAdmin;
|
||||||
|
|
||||||
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
|
// Flexible Collections V1 restricts admins, check the organization setting via canEditAllCiphers
|
||||||
|
orgAdmin = this.organization?.canEditAllCiphers(true);
|
||||||
|
}
|
||||||
|
|
||||||
return this.cipher.id == null
|
return this.cipher.id == null
|
||||||
? this.cipherService.createWithServer(cipher, orgAdmin)
|
? this.cipherService.createWithServer(cipher, orgAdmin)
|
||||||
: this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone);
|
: this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone);
|
||||||
|
|
|
@ -196,6 +196,20 @@ export class Organization {
|
||||||
return this.canEditAnyCollection;
|
return this.canEditAnyCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEditAllCiphers(flexibleCollectionsV1Enabled: boolean) {
|
||||||
|
// Before Flexible Collections, anyone with editAnyCollection permission could edit all ciphers
|
||||||
|
if (!flexibleCollectionsV1Enabled) {
|
||||||
|
return this.canEditAnyCollection;
|
||||||
|
}
|
||||||
|
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
|
||||||
|
// Providers are not affected by allowAdminAccessToAllCollectionItems flag
|
||||||
|
// note: canEditAnyCollection may change in the V1 to also ignore the allowAdminAccessToAllCollectionItems flag
|
||||||
|
return (
|
||||||
|
this.isProviderUser ||
|
||||||
|
(this.allowAdminAccessToAllCollectionItems && this.canEditAnyCollection)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get canDeleteAnyCollection() {
|
get canDeleteAnyCollection() {
|
||||||
return this.isAdmin || this.permissions.deleteAnyCollection;
|
return this.isAdmin || this.permissions.deleteAnyCollection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,11 @@ export abstract class CipherService {
|
||||||
defaultMatch?: UriMatchType,
|
defaultMatch?: UriMatchType,
|
||||||
) => Promise<CipherView[]>;
|
) => Promise<CipherView[]>;
|
||||||
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
||||||
|
/**
|
||||||
|
* Gets ciphers belonging to the specified organization that the user has explicit collection level access to.
|
||||||
|
* Ciphers that are not assigned to any collections are only included for users with admin access.
|
||||||
|
*/
|
||||||
|
getManyFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
||||||
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
||||||
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
||||||
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ApiService } from "../../abstractions/api.service";
|
||||||
import { SearchService } from "../../abstractions/search.service";
|
import { SearchService } from "../../abstractions/search.service";
|
||||||
import { SettingsService } from "../../abstractions/settings.service";
|
import { SettingsService } from "../../abstractions/settings.service";
|
||||||
import { ErrorResponse } from "../../models/response/error.response";
|
import { ErrorResponse } from "../../models/response/error.response";
|
||||||
|
import { ListResponse } from "../../models/response/list.response";
|
||||||
import { View } from "../../models/view/view";
|
import { View } from "../../models/view/view";
|
||||||
import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||||
|
@ -387,6 +388,24 @@ export class CipherService implements CipherServiceAbstraction {
|
||||||
|
|
||||||
async getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
async getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
||||||
const response = await this.apiService.getCiphersOrganization(organizationId);
|
const response = await this.apiService.getCiphersOrganization(organizationId);
|
||||||
|
return await this.decryptOrganizationCiphersResponse(response, organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getManyFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
||||||
|
const response = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
"/ciphers/organization-details/assigned?organizationId=" + organizationId,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return this.decryptOrganizationCiphersResponse(response, organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptOrganizationCiphersResponse(
|
||||||
|
response: ListResponse<CipherResponse>,
|
||||||
|
organizationId: string,
|
||||||
|
): Promise<CipherView[]> {
|
||||||
if (response?.data == null || response.data.length < 1) {
|
if (response?.data == null || response.data.length < 1) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue