mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-17154] Limit item deletion feature flag removal (#15094)
* Refactor components to remove limitItemDeletion feature flag usage This commit simplifies the logic in various components by removing the limitItemDeletion feature flag. The conditions for displaying restore and delete actions are now based solely on the cipher's permissions, enhancing code clarity and maintainability. * Refactor cipher deletion logic to remove the feature flag and collection ID dependency This commit updates the cipher deletion logic across multiple components and services by removing the unnecessary dependency on collection IDs. The `canDeleteCipher$` method now solely relies on the cipher's permissions, simplifying the code and improving maintainability. * Remove LimitItemDeletion feature flag from feature-flag enum and default values * Remove configService from ServiceContainer and MainBackground constructor parameters * Remove configService from RestoreCommand instantiation in OssServeConfigurator and VaultProgram classes
This commit is contained in:
@@ -1325,7 +1325,6 @@ export default class MainBackground {
|
|||||||
this.collectionService,
|
this.collectionService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.configService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||||
|
|||||||
@@ -17,11 +17,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
*ngIf="
|
*ngIf="cipher.isDeleted && cipher.permissions.restore"
|
||||||
(limitItemDeletion$ | async)
|
|
||||||
? cipher.isDeleted && cipher.permissions.restore
|
|
||||||
: cipher.isDeleted && cipher.edit
|
|
||||||
"
|
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
type="button"
|
type="button"
|
||||||
bitButton
|
bitButton
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Component } from "@angular/core";
|
|||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
import { firstValueFrom, Observable, switchMap, of } from "rxjs";
|
||||||
|
|
||||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
@@ -22,8 +22,6 @@ import {
|
|||||||
UPDATE_PASSWORD,
|
UPDATE_PASSWORD,
|
||||||
} from "@bitwarden/common/autofill/constants";
|
} from "@bitwarden/common/autofill/constants";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
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 { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
@@ -112,7 +110,6 @@ export class ViewV2Component {
|
|||||||
loadAction: LoadAction;
|
loadAction: LoadAction;
|
||||||
senderTabId?: number;
|
senderTabId?: number;
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
protected showFooter$: Observable<boolean>;
|
protected showFooter$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -131,7 +128,6 @@ export class ViewV2Component {
|
|||||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||||
private copyCipherFieldService: CopyCipherFieldService,
|
private copyCipherFieldService: CopyCipherFieldService,
|
||||||
private popupScrollPositionService: VaultPopupScrollPositionService,
|
private popupScrollPositionService: VaultPopupScrollPositionService,
|
||||||
private configService: ConfigService,
|
|
||||||
) {
|
) {
|
||||||
this.subscribeToParams();
|
this.subscribeToParams();
|
||||||
}
|
}
|
||||||
@@ -160,17 +156,10 @@ export class ViewV2Component {
|
|||||||
|
|
||||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(cipher);
|
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(cipher);
|
||||||
|
|
||||||
this.showFooter$ = this.limitItemDeletion$.pipe(
|
this.showFooter$ = of(
|
||||||
map((enabled) => {
|
cipher &&
|
||||||
if (enabled) {
|
(!cipher.isDeleted ||
|
||||||
return (
|
(cipher.isDeleted && (cipher.permissions.restore || cipher.permissions.delete))),
|
||||||
cipher &&
|
|
||||||
(!cipher.isDeleted ||
|
|
||||||
(cipher.isDeleted && (cipher.permissions.restore || cipher.permissions.delete)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.showFooterLegacy();
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.eventCollectionService.collect(
|
await this.eventCollectionService.collect(
|
||||||
@@ -268,15 +257,6 @@ export class ViewV2Component {
|
|||||||
: this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId);
|
: this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//@TODO: remove this when the LimitItemDeletion feature flag is removed
|
|
||||||
protected showFooterLegacy(): boolean {
|
|
||||||
return (
|
|
||||||
this.cipher &&
|
|
||||||
(!this.cipher.isDeleted ||
|
|
||||||
(this.cipher.isDeleted && this.cipher.edit && this.cipher.viewPassword))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the load action for the view vault item popout. These actions are typically triggered
|
* Handles the load action for the view vault item popout. These actions are typically triggered
|
||||||
* via the extension context menu. It is necessary to render the view for items that have password
|
* via the extension context menu. It is necessary to render the view for items that have password
|
||||||
|
|||||||
@@ -31,14 +31,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||||
</button>
|
</button>
|
||||||
<ng-container
|
<ng-container slot="end" *ngIf="cipher.permissions.restore">
|
||||||
slot="end"
|
|
||||||
*ngIf="
|
|
||||||
(limitItemDeletion$ | async)
|
|
||||||
? cipher.permissions.restore
|
|
||||||
: cipher.edit && cipher.viewPassword
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<bit-item-action>
|
<bit-item-action>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { firstValueFrom } from "rxjs";
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/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 { 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 { CipherId } from "@bitwarden/common/types/guid";
|
import { CipherId } from "@bitwarden/common/types/guid";
|
||||||
@@ -70,11 +68,8 @@ export class TrashListItemsContainerComponent {
|
|||||||
private passwordRepromptService: PasswordRepromptService,
|
private passwordRepromptService: PasswordRepromptService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tooltip text for the organization icon for ciphers that belong to an organization.
|
* The tooltip text for the organization icon for ciphers that belong to an organization.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { combineLatest, firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||||
|
|
||||||
@@ -13,7 +11,6 @@ export class RestoreCommand {
|
|||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private configService: ConfigService,
|
|
||||||
private cipherAuthorizationService: CipherAuthorizationService,
|
private cipherAuthorizationService: CipherAuthorizationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -42,17 +39,7 @@ export class RestoreCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canRestore = await firstValueFrom(
|
const canRestore = await firstValueFrom(
|
||||||
combineLatest([
|
this.cipherAuthorizationService.canRestoreCipher$(cipher),
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion),
|
|
||||||
this.cipherAuthorizationService.canRestoreCipher$(cipher),
|
|
||||||
]).pipe(
|
|
||||||
map(([enabled, canRestore]) => {
|
|
||||||
if (enabled && !canRestore) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!canRestore) {
|
if (!canRestore) {
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ export class OssServeConfigurator {
|
|||||||
this.restoreCommand = new RestoreCommand(
|
this.restoreCommand = new RestoreCommand(
|
||||||
this.serviceContainer.cipherService,
|
this.serviceContainer.cipherService,
|
||||||
this.serviceContainer.accountService,
|
this.serviceContainer.accountService,
|
||||||
this.serviceContainer.configService,
|
|
||||||
this.serviceContainer.cipherAuthorizationService,
|
this.serviceContainer.cipherAuthorizationService,
|
||||||
);
|
);
|
||||||
this.shareCommand = new ShareCommand(
|
this.shareCommand = new ShareCommand(
|
||||||
|
|||||||
@@ -861,7 +861,6 @@ export class ServiceContainer {
|
|||||||
this.collectionService,
|
this.collectionService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.configService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService);
|
this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService);
|
||||||
|
|||||||
@@ -350,7 +350,6 @@ export class VaultProgram extends BaseProgram {
|
|||||||
const command = new RestoreCommand(
|
const command = new RestoreCommand(
|
||||||
this.serviceContainer.cipherService,
|
this.serviceContainer.cipherService,
|
||||||
this.serviceContainer.accountService,
|
this.serviceContainer.accountService,
|
||||||
this.serviceContainer.configService,
|
|
||||||
this.serviceContainer.cipherAuthorizationService,
|
this.serviceContainer.cipherAuthorizationService,
|
||||||
);
|
);
|
||||||
const response = await command.run(object, id);
|
const response = await command.run(object, id);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
|||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
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 { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -49,9 +49,7 @@ export class ItemFooterComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [
|
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher);
|
||||||
this.collectionId as CollectionId,
|
|
||||||
]);
|
|
||||||
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -656,11 +656,7 @@
|
|||||||
class="primary"
|
class="primary"
|
||||||
(click)="restore()"
|
(click)="restore()"
|
||||||
appA11yTitle="{{ 'restore' | i18n }}"
|
appA11yTitle="{{ 'restore' | i18n }}"
|
||||||
*ngIf="
|
*ngIf="(canRestoreCipher$ | async) && cipher.isDeleted"
|
||||||
(limitItemDeletion$ | async)
|
|
||||||
? (canRestoreCipher$ | async) && cipher.isDeleted
|
|
||||||
: cipher.isDeleted
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-undo bwi-fw bwi-lg" aria-hidden="true"></i>
|
<i class="bwi bwi-undo bwi-fw bwi-lg" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
|||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@@ -108,8 +107,6 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<bit-label>{{ "limitCollectionDeletionDesc" | i18n }}</bit-label>
|
<bit-label>{{ "limitCollectionDeletionDesc" | i18n }}</bit-label>
|
||||||
<input type="checkbox" bitCheckbox formControlName="limitCollectionDeletion" />
|
<input type="checkbox" bitCheckbox formControlName="limitCollectionDeletion" />
|
||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
<bit-form-control *ngIf="limitItemDeletionFeatureFlagIsEnabled">
|
<bit-form-control>
|
||||||
<bit-label>{{ "limitItemDeletionDescription" | i18n }}</bit-label>
|
<bit-label>{{ "limitItemDeletionDescription" | i18n }}</bit-label>
|
||||||
<input type="checkbox" bitCheckbox formControlName="limitItemDeletion" />
|
<input type="checkbox" bitCheckbox formControlName="limitItemDeletion" />
|
||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/model
|
|||||||
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
|
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
@@ -51,8 +49,6 @@ export class AccountComponent implements OnInit, OnDestroy {
|
|||||||
org: OrganizationResponse;
|
org: OrganizationResponse;
|
||||||
taxFormPromise: Promise<unknown>;
|
taxFormPromise: Promise<unknown>;
|
||||||
|
|
||||||
limitItemDeletionFeatureFlagIsEnabled: boolean;
|
|
||||||
|
|
||||||
// FormGroup validators taken from server Organization domain object
|
// FormGroup validators taken from server Organization domain object
|
||||||
protected formGroup = this.formBuilder.group({
|
protected formGroup = this.formBuilder.group({
|
||||||
orgName: this.formBuilder.control(
|
orgName: this.formBuilder.control(
|
||||||
@@ -95,17 +91,11 @@ export class AccountComponent implements OnInit, OnDestroy {
|
|||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||||
|
|
||||||
this.configService
|
|
||||||
.getFeatureFlag$(FeatureFlag.LimitItemDeletion)
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe((isAble) => (this.limitItemDeletionFeatureFlagIsEnabled = isAble));
|
|
||||||
|
|
||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.route.params
|
this.route.params
|
||||||
.pipe(
|
.pipe(
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
|||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
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";
|
||||||
@@ -228,10 +226,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
* A user may restore items if they have delete permissions and the item is in the trash.
|
* A user may restore items if they have delete permissions and the item is in the trash.
|
||||||
*/
|
*/
|
||||||
protected async canUserRestore() {
|
protected async canUserRestore() {
|
||||||
if (await firstValueFrom(this.limitItemDeletion$)) {
|
return this.isTrashFilter && this.cipher?.isDeleted && this.cipher?.permissions.restore;
|
||||||
return this.isTrashFilter && this.cipher?.isDeleted && this.cipher?.permissions.restore;
|
|
||||||
}
|
|
||||||
return this.isTrashFilter && this.cipher?.isDeleted && this.canDelete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected showRestore: boolean;
|
protected showRestore: boolean;
|
||||||
@@ -277,8 +272,6 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected canDelete = false;
|
protected canDelete = false;
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) protected params: VaultItemDialogParams,
|
@Inject(DIALOG_DATA) protected params: VaultItemDialogParams,
|
||||||
private dialogRef: DialogRef<VaultItemDialogResult>,
|
private dialogRef: DialogRef<VaultItemDialogResult>,
|
||||||
@@ -296,7 +289,6 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private eventCollectionService: EventCollectionService,
|
private eventCollectionService: EventCollectionService,
|
||||||
private routedVaultFilterService: RoutedVaultFilterService,
|
private routedVaultFilterService: RoutedVaultFilterService,
|
||||||
private configService: ConfigService,
|
|
||||||
) {
|
) {
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
}
|
}
|
||||||
@@ -323,7 +315,6 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.canDelete = await firstValueFrom(
|
this.canDelete = await firstValueFrom(
|
||||||
this.cipherAuthorizationService.canDeleteCipher$(
|
this.cipherAuthorizationService.canDeleteCipher$(
|
||||||
this.cipher,
|
this.cipher,
|
||||||
[this.params.activeCollectionId],
|
|
||||||
this.params.isAdminConsoleAction,
|
this.params.isAdminConsoleAction,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -86,12 +86,7 @@
|
|||||||
appStopProp
|
appStopProp
|
||||||
></button>
|
></button>
|
||||||
<bit-menu #corruptedCipherOptions>
|
<bit-menu #corruptedCipherOptions>
|
||||||
<button
|
<button bitMenuItem *ngIf="canDeleteCipher" (click)="deleteCipher()" type="button">
|
||||||
bitMenuItem
|
|
||||||
*ngIf="(limitItemDeletion$ | async) ? canDeleteCipher : canManageCollection"
|
|
||||||
(click)="deleteCipher()"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span class="tw-text-danger">
|
<span class="tw-text-danger">
|
||||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||||
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||||
@@ -160,17 +155,12 @@
|
|||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="restore()"
|
(click)="restore()"
|
||||||
type="button"
|
type="button"
|
||||||
*ngIf="(limitItemDeletion$ | async) ? cipher.isDeleted && canRestoreCipher : cipher.isDeleted"
|
*ngIf="cipher.isDeleted && canRestoreCipher"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||||
{{ "restore" | i18n }}
|
{{ "restore" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button bitMenuItem *ngIf="canDeleteCipher" (click)="deleteCipher()" type="button">
|
||||||
bitMenuItem
|
|
||||||
*ngIf="(limitItemDeletion$ | async) ? canDeleteCipher : canManageCollection"
|
|
||||||
(click)="deleteCipher()"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span class="tw-text-danger">
|
<span class="tw-text-danger">
|
||||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||||
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
|||||||
|
|
||||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -53,7 +51,6 @@ export class VaultCipherRowComponent implements OnInit {
|
|||||||
@Input() checked: boolean;
|
@Input() checked: boolean;
|
||||||
@Output() checkedToggled = new EventEmitter<void>();
|
@Output() checkedToggled = new EventEmitter<void>();
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
protected CipherType = CipherType;
|
protected CipherType = CipherType;
|
||||||
private permissionList = getPermissionList();
|
private permissionList = getPermissionList();
|
||||||
private permissionPriority = [
|
private permissionPriority = [
|
||||||
@@ -65,10 +62,7 @@ export class VaultCipherRowComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
protected organization?: Organization;
|
protected organization?: Organization;
|
||||||
|
|
||||||
constructor(
|
constructor(private i18nService: I18nService) {}
|
||||||
private i18nService: I18nService,
|
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle hook for component initialization.
|
* Lifecycle hook for component initialization.
|
||||||
|
|||||||
@@ -52,12 +52,11 @@
|
|||||||
{{ "permission" | i18n }}
|
{{ "permission" | i18n }}
|
||||||
</th>
|
</th>
|
||||||
<th bitCell class="tw-w-12 tw-text-right">
|
<th bitCell class="tw-w-12 tw-text-right">
|
||||||
@let featureFlaggedDisable =
|
@let menuDisabled = disableMenu$ | async;
|
||||||
(limitItemDeletion$ | async) ? (disableMenu$ | async) : disableMenu;
|
|
||||||
<button
|
<button
|
||||||
[disabled]="disabled || isEmpty || featureFlaggedDisable"
|
[disabled]="disabled || isEmpty || menuDisabled"
|
||||||
[bitMenuTriggerFor]="headerMenu"
|
[bitMenuTriggerFor]="headerMenu"
|
||||||
[attr.title]="featureFlaggedDisable ? ('missingPermissions' | i18n) : ''"
|
[attr.title]="menuDisabled ? ('missingPermissions' | i18n) : ''"
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
size="small"
|
size="small"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -89,9 +88,7 @@
|
|||||||
{{ "assignToCollections" | i18n }}
|
{{ "assignToCollections" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="
|
*ngIf="canRestoreSelected$ | async"
|
||||||
(limitItemDeletion$ | async) ? (canRestoreSelected$ | async) : showBulkTrashOptions
|
|
||||||
"
|
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="bulkRestore()"
|
(click)="bulkRestore()"
|
||||||
@@ -100,7 +97,7 @@
|
|||||||
{{ "restoreSelected" | i18n }}
|
{{ "restoreSelected" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="(limitItemDeletion$ | async) ? (canDeleteSelected$ | async) : showDelete"
|
*ngIf="canDeleteSelected$ | async"
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="bulkDelete()"
|
(click)="bulkDelete()"
|
||||||
@@ -161,11 +158,7 @@
|
|||||||
[canAssignCollections]="canAssignCollections(item.cipher)"
|
[canAssignCollections]="canAssignCollections(item.cipher)"
|
||||||
[canManageCollection]="canManageCollection(item.cipher)"
|
[canManageCollection]="canManageCollection(item.cipher)"
|
||||||
[canDeleteCipher]="
|
[canDeleteCipher]="
|
||||||
cipherAuthorizationService.canDeleteCipher$(
|
cipherAuthorizationService.canDeleteCipher$(item.cipher, showAdminActions) | async
|
||||||
item.cipher,
|
|
||||||
[item.cipher.collectionId],
|
|
||||||
showAdminActions
|
|
||||||
) | async
|
|
||||||
"
|
"
|
||||||
[canRestoreCipher]="
|
[canRestoreCipher]="
|
||||||
cipherAuthorizationService.canRestoreCipher$(item.cipher, showAdminActions) | async
|
cipherAuthorizationService.canRestoreCipher$(item.cipher, showAdminActions) | async
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs";
|
|||||||
|
|
||||||
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
|
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||||
import { SortDirection, TableDataSource } from "@bitwarden/components";
|
import { SortDirection, TableDataSource } from "@bitwarden/components";
|
||||||
@@ -78,7 +76,6 @@ export class VaultItemsComponent {
|
|||||||
|
|
||||||
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
||||||
|
|
||||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
|
||||||
protected editableItems: VaultItem[] = [];
|
protected editableItems: VaultItem[] = [];
|
||||||
protected dataSource = new TableDataSource<VaultItem>();
|
protected dataSource = new TableDataSource<VaultItem>();
|
||||||
protected selection = new SelectionModel<VaultItem>(true, [], true);
|
protected selection = new SelectionModel<VaultItem>(true, [], true);
|
||||||
@@ -86,10 +83,7 @@ export class VaultItemsComponent {
|
|||||||
protected canRestoreSelected$: Observable<boolean>;
|
protected canRestoreSelected$: Observable<boolean>;
|
||||||
protected disableMenu$: Observable<boolean>;
|
protected disableMenu$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(protected cipherAuthorizationService: CipherAuthorizationService) {
|
||||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
|
||||||
private configService: ConfigService,
|
|
||||||
) {
|
|
||||||
this.canDeleteSelected$ = this.selection.changed.pipe(
|
this.canDeleteSelected$ = this.selection.changed.pipe(
|
||||||
startWith(null),
|
startWith(null),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
@@ -102,7 +96,7 @@ export class VaultItemsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canDeleteCiphers$ = ciphers.map((c) =>
|
const canDeleteCiphers$ = ciphers.map((c) =>
|
||||||
cipherAuthorizationService.canDeleteCipher$(c, [], this.showAdminActions),
|
cipherAuthorizationService.canDeleteCipher$(c, this.showAdminActions),
|
||||||
);
|
);
|
||||||
|
|
||||||
const canDeleteCollections = this.selection.selected
|
const canDeleteCollections = this.selection.selected
|
||||||
@@ -141,17 +135,14 @@ export class VaultItemsComponent {
|
|||||||
map((canRestore) => canRestore && this.showBulkTrashOptions),
|
map((canRestore) => canRestore && this.showBulkTrashOptions),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.disableMenu$ = combineLatest([this.limitItemDeletion$, this.canDeleteSelected$]).pipe(
|
this.disableMenu$ = this.canDeleteSelected$.pipe(
|
||||||
map(([enabled, canDelete]) => {
|
map((canDelete) => {
|
||||||
if (enabled) {
|
return (
|
||||||
return (
|
!this.bulkMoveAllowed &&
|
||||||
!this.bulkMoveAllowed &&
|
!this.showAssignToCollections() &&
|
||||||
!this.showAssignToCollections() &&
|
!canDelete &&
|
||||||
!canDelete &&
|
!this.showBulkEditCollectionAccess
|
||||||
!this.showBulkEditCollectionAccess
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -205,15 +196,6 @@ export class VaultItemsComponent {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get disableMenu() {
|
|
||||||
return (
|
|
||||||
!this.bulkMoveAllowed &&
|
|
||||||
!this.showAssignToCollections() &&
|
|
||||||
!this.showDelete &&
|
|
||||||
!this.showBulkEditCollectionAccess
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bulkAssignToCollectionsAllowed() {
|
get bulkAssignToCollectionsAllowed() {
|
||||||
return this.showBulkAddToCollections && this.ciphers.length > 0;
|
return this.showBulkAddToCollections && this.ciphers.length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,9 +123,7 @@ export class ViewComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [
|
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher);
|
||||||
this.params.activeCollectionId,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1462,12 +1462,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: CipherAuthorizationService,
|
provide: CipherAuthorizationService,
|
||||||
useClass: DefaultCipherAuthorizationService,
|
useClass: DefaultCipherAuthorizationService,
|
||||||
deps: [
|
deps: [CollectionService, OrganizationServiceAbstraction, AccountServiceAbstraction],
|
||||||
CollectionService,
|
|
||||||
OrganizationServiceAbstraction,
|
|
||||||
AccountServiceAbstraction,
|
|
||||||
ConfigService,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: AuthRequestApiService,
|
provide: AuthRequestApiService,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import {
|
import {
|
||||||
CipherService,
|
CipherService,
|
||||||
EncryptionContext,
|
EncryptionContext,
|
||||||
@@ -348,7 +348,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(
|
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(
|
||||||
this.cipher,
|
this.cipher,
|
||||||
[this.collectionId as CollectionId],
|
|
||||||
this.isAdminConsoleAction,
|
this.isAdminConsoleAction,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { CipherId, CollectionId, UserId } from "@bitwarden/common/types/guid";
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
@@ -521,9 +521,7 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
);
|
);
|
||||||
this.showPremiumRequiredTotp =
|
this.showPremiumRequiredTotp =
|
||||||
this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp;
|
this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp;
|
||||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [
|
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher);
|
||||||
this.collectionId as CollectionId,
|
|
||||||
]);
|
|
||||||
this.canRestoreCipher$ = this.cipherAuthorizationService.canRestoreCipher$(this.cipher);
|
this.canRestoreCipher$ = this.cipherAuthorizationService.canRestoreCipher$(this.cipher);
|
||||||
|
|
||||||
if (this.cipher.folderId) {
|
if (this.cipher.folderId) {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { ServerConfig } from "../platform/abstractions/config/server-config";
|
|||||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
/* Admin Console Team */
|
/* Admin Console Team */
|
||||||
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
|
|
||||||
SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions",
|
SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions",
|
||||||
OptimizeNestedTraverseTypescript = "pm-21695-optimize-nested-traverse-typescript",
|
OptimizeNestedTraverseTypescript = "pm-21695-optimize-nested-traverse-typescript",
|
||||||
|
|
||||||
@@ -75,7 +74,6 @@ const FALSE = false as boolean;
|
|||||||
*/
|
*/
|
||||||
export const DefaultFeatureFlagValue = {
|
export const DefaultFeatureFlagValue = {
|
||||||
/* Admin Console Team */
|
/* Admin Console Team */
|
||||||
[FeatureFlag.LimitItemDeletion]: FALSE,
|
|
||||||
[FeatureFlag.SeparateCustomRolePermissions]: FALSE,
|
[FeatureFlag.SeparateCustomRolePermissions]: FALSE,
|
||||||
[FeatureFlag.OptimizeNestedTraverseTypescript]: FALSE,
|
[FeatureFlag.OptimizeNestedTraverseTypescript]: FALSE,
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm
|
|||||||
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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
||||||
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
|
||||||
import { CipherPermissionsApi } from "../models/api/cipher-permissions.api";
|
import { CipherPermissionsApi } from "../models/api/cipher-permissions.api";
|
||||||
import { CipherView } from "../models/view/cipher.view";
|
import { CipherView } from "../models/view/cipher.view";
|
||||||
|
|
||||||
@@ -24,7 +23,6 @@ describe("CipherAuthorizationService", () => {
|
|||||||
|
|
||||||
const mockCollectionService = mock<CollectionService>();
|
const mockCollectionService = mock<CollectionService>();
|
||||||
const mockOrganizationService = mock<OrganizationService>();
|
const mockOrganizationService = mock<OrganizationService>();
|
||||||
const mockConfigService = mock<ConfigService>();
|
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
let mockAccountService: FakeAccountService;
|
let mockAccountService: FakeAccountService;
|
||||||
|
|
||||||
@@ -70,10 +68,7 @@ describe("CipherAuthorizationService", () => {
|
|||||||
mockCollectionService,
|
mockCollectionService,
|
||||||
mockOrganizationService,
|
mockOrganizationService,
|
||||||
mockAccountService,
|
mockAccountService,
|
||||||
mockConfigService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("canRestoreCipher$", () => {
|
describe("canRestoreCipher$", () => {
|
||||||
@@ -90,7 +85,7 @@ describe("CipherAuthorizationService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return true if isAdminConsleAction and user can edit all ciphers in the org", (done) => {
|
it("should return true if isAdminConsoleAction and user can edit all ciphers in the org", (done) => {
|
||||||
const cipher = createMockCipher("org1", ["col1"]) as CipherView;
|
const cipher = createMockCipher("org1", ["col1"]) as CipherView;
|
||||||
const organization = createMockOrganization({ canEditAllCiphers: true });
|
const organization = createMockOrganization({ canEditAllCiphers: true });
|
||||||
mockOrganizationService.organizations$.mockReturnValue(
|
mockOrganizationService.organizations$.mockReturnValue(
|
||||||
@@ -145,15 +140,6 @@ describe("CipherAuthorizationService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("canDeleteCipher$", () => {
|
describe("canDeleteCipher$", () => {
|
||||||
it("should return true if cipher has no organizationId", (done) => {
|
|
||||||
const cipher = createMockCipher(null, []) as CipherView;
|
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
|
||||||
expect(result).toBe(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return true if isAdminConsoleAction is true and cipher is unassigned", (done) => {
|
it("should return true if isAdminConsoleAction is true and cipher is unassigned", (done) => {
|
||||||
const cipher = createMockCipher("org1", []) as CipherView;
|
const cipher = createMockCipher("org1", []) as CipherView;
|
||||||
const organization = createMockOrganization({ canEditUnassignedCiphers: true });
|
const organization = createMockOrganization({ canEditUnassignedCiphers: true });
|
||||||
@@ -161,7 +147,7 @@ describe("CipherAuthorizationService", () => {
|
|||||||
of([organization]) as Observable<Organization[]>,
|
of([organization]) as Observable<Organization[]>,
|
||||||
);
|
);
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -174,7 +160,7 @@ describe("CipherAuthorizationService", () => {
|
|||||||
of([organization]) as Observable<Organization[]>,
|
of([organization]) as Observable<Organization[]>,
|
||||||
);
|
);
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId);
|
expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId);
|
||||||
done();
|
done();
|
||||||
@@ -186,136 +172,32 @@ describe("CipherAuthorizationService", () => {
|
|||||||
const organization = createMockOrganization({ canEditUnassignedCiphers: false });
|
const organization = createMockOrganization({ canEditUnassignedCiphers: false });
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return true if activeCollectionId is provided and has manage permission", (done) => {
|
it("should return true when cipher.permissions.delete is true", (done) => {
|
||||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
|
||||||
const activeCollectionId = "col1" as CollectionId;
|
|
||||||
const organization = createMockOrganization();
|
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
|
||||||
|
|
||||||
const allCollections = [
|
|
||||||
createMockCollection("col1", true),
|
|
||||||
createMockCollection("col2", false),
|
|
||||||
];
|
|
||||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
|
||||||
of(allCollections as CollectionView[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
cipherAuthorizationService
|
|
||||||
.canDeleteCipher$(cipher, [activeCollectionId])
|
|
||||||
.subscribe((result) => {
|
|
||||||
expect(result).toBe(true);
|
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
|
||||||
"col1",
|
|
||||||
"col2",
|
|
||||||
] as CollectionId[]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return false if activeCollectionId is provided and manage permission is not present", (done) => {
|
|
||||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
|
||||||
const activeCollectionId = "col1" as CollectionId;
|
|
||||||
const organization = createMockOrganization();
|
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
|
||||||
|
|
||||||
const allCollections = [
|
|
||||||
createMockCollection("col1", false),
|
|
||||||
createMockCollection("col2", true),
|
|
||||||
];
|
|
||||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
|
||||||
of(allCollections as CollectionView[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
cipherAuthorizationService
|
|
||||||
.canDeleteCipher$(cipher, [activeCollectionId])
|
|
||||||
.subscribe((result) => {
|
|
||||||
expect(result).toBe(false);
|
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
|
||||||
"col1",
|
|
||||||
"col2",
|
|
||||||
] as CollectionId[]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return true if any collection has manage permission", (done) => {
|
|
||||||
const cipher = createMockCipher("org1", ["col1", "col2", "col3"]) as CipherView;
|
|
||||||
const organization = createMockOrganization();
|
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
|
||||||
|
|
||||||
const allCollections = [
|
|
||||||
createMockCollection("col1", false),
|
|
||||||
createMockCollection("col2", true),
|
|
||||||
createMockCollection("col3", false),
|
|
||||||
];
|
|
||||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
|
||||||
of(allCollections as CollectionView[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
|
||||||
expect(result).toBe(true);
|
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
|
||||||
"col1",
|
|
||||||
"col2",
|
|
||||||
"col3",
|
|
||||||
] as CollectionId[]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return false if no collection has manage permission", (done) => {
|
|
||||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
|
||||||
const organization = createMockOrganization();
|
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
|
||||||
|
|
||||||
const allCollections = [
|
|
||||||
createMockCollection("col1", false),
|
|
||||||
createMockCollection("col2", false),
|
|
||||||
];
|
|
||||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
|
||||||
of(allCollections as CollectionView[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
|
||||||
expect(result).toBe(false);
|
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
|
||||||
"col1",
|
|
||||||
"col2",
|
|
||||||
] as CollectionId[]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return true if feature flag enabled and cipher.permissions.delete is true", (done) => {
|
|
||||||
const cipher = createMockCipher("org1", [], true, {
|
const cipher = createMockCipher("org1", [], true, {
|
||||||
delete: true,
|
delete: true,
|
||||||
} as CipherPermissionsApi) as CipherView;
|
} as CipherPermissionsApi) as CipherView;
|
||||||
const organization = createMockOrganization();
|
const organization = createMockOrganization();
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
|
cipherAuthorizationService.canDeleteCipher$(cipher, false).subscribe((result) => {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false if feature flag enabled and cipher.permissions.delete is false", (done) => {
|
it("should return false when cipher.permissions.delete is false", (done) => {
|
||||||
const cipher = createMockCipher("org1", []) as CipherView;
|
const cipher = createMockCipher("org1", []) as CipherView;
|
||||||
const organization = createMockOrganization();
|
const organization = createMockOrganization();
|
||||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
|
||||||
|
|
||||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
|
cipherAuthorizationService.canDeleteCipher$(cipher, false).subscribe((result) => {
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs";
|
import { map, Observable, of, shareReplay, switchMap } from "rxjs";
|
||||||
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import { getUserId } from "../../auth/services/account.service";
|
import { getUserId } from "../../auth/services/account.service";
|
||||||
import { FeatureFlag } from "../../enums/feature-flag.enum";
|
|
||||||
import { Cipher } from "../models/domain/cipher";
|
import { Cipher } from "../models/domain/cipher";
|
||||||
import { CipherView } from "../models/view/cipher.view";
|
import { CipherView } from "../models/view/cipher.view";
|
||||||
|
|
||||||
@@ -26,14 +24,12 @@ export abstract class CipherAuthorizationService {
|
|||||||
* Determines if the user can delete the specified cipher.
|
* Determines if the user can delete the specified cipher.
|
||||||
*
|
*
|
||||||
* @param {CipherLike} cipher - The cipher object to evaluate for deletion permissions.
|
* @param {CipherLike} cipher - The cipher object to evaluate for deletion permissions.
|
||||||
* @param {CollectionId[]} [allowedCollections] - Optional. The selected collection id from the vault filter.
|
|
||||||
* @param {boolean} isAdminConsoleAction - Optional. A flag indicating if the action is being performed from the admin console.
|
* @param {boolean} isAdminConsoleAction - Optional. A flag indicating if the action is being performed from the admin console.
|
||||||
*
|
*
|
||||||
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can delete the cipher.
|
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can delete the cipher.
|
||||||
*/
|
*/
|
||||||
abstract canDeleteCipher$: (
|
abstract canDeleteCipher$: (
|
||||||
cipher: CipherLike,
|
cipher: CipherLike,
|
||||||
allowedCollections?: CollectionId[],
|
|
||||||
isAdminConsoleAction?: boolean,
|
isAdminConsoleAction?: boolean,
|
||||||
) => Observable<boolean>;
|
) => Observable<boolean>;
|
||||||
|
|
||||||
@@ -72,7 +68,6 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
|||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private organization$ = (cipher: CipherLike) =>
|
private organization$ = (cipher: CipherLike) =>
|
||||||
@@ -86,48 +81,21 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
|||||||
*
|
*
|
||||||
* {@link CipherAuthorizationService.canDeleteCipher$}
|
* {@link CipherAuthorizationService.canDeleteCipher$}
|
||||||
*/
|
*/
|
||||||
canDeleteCipher$(
|
canDeleteCipher$(cipher: CipherLike, isAdminConsoleAction?: boolean): Observable<boolean> {
|
||||||
cipher: CipherLike,
|
return this.organization$(cipher).pipe(
|
||||||
allowedCollections?: CollectionId[],
|
map((organization) => {
|
||||||
isAdminConsoleAction?: boolean,
|
|
||||||
): Observable<boolean> {
|
|
||||||
return combineLatest([
|
|
||||||
this.organization$(cipher),
|
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion),
|
|
||||||
]).pipe(
|
|
||||||
switchMap(([organization, featureFlagEnabled]) => {
|
|
||||||
if (isAdminConsoleAction) {
|
if (isAdminConsoleAction) {
|
||||||
// If the user is an admin, they can delete an unassigned cipher
|
// If the user is an admin, they can delete an unassigned cipher
|
||||||
if (!cipher.collectionIds || cipher.collectionIds.length === 0) {
|
if (!cipher.collectionIds || cipher.collectionIds.length === 0) {
|
||||||
return of(organization?.canEditUnassignedCiphers === true);
|
return organization?.canEditUnassignedCiphers === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization?.canEditAllCiphers) {
|
if (organization?.canEditAllCiphers) {
|
||||||
return of(true);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (featureFlagEnabled) {
|
return cipher.permissions.delete;
|
||||||
return of(cipher.permissions.delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cipher.organizationId == null) {
|
|
||||||
return of(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.collectionService
|
|
||||||
.decryptedCollectionViews$(cipher.collectionIds as CollectionId[])
|
|
||||||
.pipe(
|
|
||||||
map((allCollections) => {
|
|
||||||
const shouldFilter = allowedCollections?.some(Boolean);
|
|
||||||
|
|
||||||
const collections = shouldFilter
|
|
||||||
? allCollections.filter((c) => allowedCollections?.includes(c.id as CollectionId))
|
|
||||||
: allCollections;
|
|
||||||
|
|
||||||
return collections.some((collection) => collection.manage);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user