mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-22 11:45:59 +01:00
[PM-10996] Remove restrict-provider-access feature flag (#10977)
This commit is contained in:
parent
db9003458b
commit
8e4dab5eba
@ -6,7 +6,6 @@ import { first } from "rxjs/operators";
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -29,7 +28,6 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
private route: ActivatedRoute,
|
||||
private location: Location,
|
||||
logService: LogService,
|
||||
configService: ConfigService,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
@ -40,7 +38,6 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
configService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
|
@ -3,7 +3,6 @@ import { Component } from "@angular/core";
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -23,7 +22,6 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
configService: ConfigService,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
@ -34,7 +32,6 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
configService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
|
@ -34,7 +34,6 @@ export class VaultCollectionRowComponent {
|
||||
@Input() organizations: Organization[];
|
||||
@Input() groups: GroupView[];
|
||||
@Input() showPermissionsColumn: boolean;
|
||||
@Input() restrictProviderAccess: boolean;
|
||||
|
||||
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
||||
|
||||
@ -74,10 +73,7 @@ export class VaultCollectionRowComponent {
|
||||
}
|
||||
|
||||
get permissionText() {
|
||||
if (
|
||||
this.collection.id == Unassigned &&
|
||||
this.organization?.canEditUnassignedCiphers(this.restrictProviderAccess)
|
||||
) {
|
||||
if (this.collection.id == Unassigned && this.organization?.canEditUnassignedCiphers) {
|
||||
return this.i18nService.t("canEdit");
|
||||
}
|
||||
if ((this.collection as CollectionAdminView).assigned) {
|
||||
|
@ -106,7 +106,6 @@
|
||||
[canDeleteCollection]="canDeleteCollection(item.collection)"
|
||||
[canEditCollection]="canEditCollection(item.collection)"
|
||||
[canViewCollectionInfo]="canViewCollectionInfo(item.collection)"
|
||||
[restrictProviderAccess]="restrictProviderAccess"
|
||||
[checked]="selection.isSelected(item)"
|
||||
(checkedToggled)="selection.toggle(item)"
|
||||
(onEvent)="event($event)"
|
||||
|
@ -46,7 +46,6 @@ export class VaultItemsComponent {
|
||||
@Input() viewingOrgVault: boolean;
|
||||
@Input() addAccessStatus: number;
|
||||
@Input() addAccessToggle: boolean;
|
||||
@Input() restrictProviderAccess: boolean;
|
||||
@Input() vaultBulkManagementActionEnabled = false;
|
||||
@Input() activeCollection: CollectionView | undefined;
|
||||
|
||||
@ -213,10 +212,7 @@ export class VaultItemsComponent {
|
||||
}
|
||||
|
||||
const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId);
|
||||
return (
|
||||
(organization.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault) ||
|
||||
cipher.edit
|
||||
);
|
||||
return (organization.canEditAllCiphers && this.viewingOrgVault) || cipher.edit;
|
||||
}
|
||||
|
||||
protected canManageCollection(cipher: CipherView) {
|
||||
@ -306,8 +302,7 @@ export class VaultItemsComponent {
|
||||
const [orgId] = uniqueCipherOrgIds;
|
||||
const organization = this.allOrganizations.find((o) => o.id === orgId);
|
||||
|
||||
const canEditOrManageAllCiphers =
|
||||
organization?.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault;
|
||||
const canEditOrManageAllCiphers = organization?.canEditAllCiphers && this.viewingOrgVault;
|
||||
|
||||
const collectionNotSelected =
|
||||
this.selection.selected.filter((item) => item.collection).length === 0;
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@ -54,10 +51,6 @@ export class BulkDeleteDialogComponent {
|
||||
collections: CollectionView[];
|
||||
unassignedCiphers: string[];
|
||||
|
||||
private restrictProviderAccess$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
|
||||
private dialogRef: DialogRef<BulkDeleteDialogResult>,
|
||||
@ -66,7 +59,6 @@ export class BulkDeleteDialogComponent {
|
||||
private i18nService: I18nService,
|
||||
private apiService: ApiService,
|
||||
private collectionService: CollectionService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.cipherIds = params.cipherIds ?? [];
|
||||
this.permanent = params.permanent;
|
||||
@ -82,19 +74,13 @@ export class BulkDeleteDialogComponent {
|
||||
|
||||
protected submit = async () => {
|
||||
const deletePromises: Promise<void>[] = [];
|
||||
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
|
||||
|
||||
// Unassigned ciphers under an Owner/Admin OR Custom Users With Edit will call the deleteCiphersAdmin method
|
||||
if (
|
||||
this.unassignedCiphers.length &&
|
||||
this.organization.canEditUnassignedCiphers(restrictProviderAccess)
|
||||
) {
|
||||
if (this.unassignedCiphers.length && this.organization.canEditUnassignedCiphers) {
|
||||
deletePromises.push(this.deleteCiphersAdmin(this.unassignedCiphers));
|
||||
}
|
||||
if (this.cipherIds.length) {
|
||||
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
|
||||
|
||||
if (!this.organization || !this.organization.canEditAllCiphers(restrictProviderAccess)) {
|
||||
if (!this.organization || !this.organization.canEditAllCiphers) {
|
||||
deletePromises.push(this.deleteCiphers());
|
||||
} else {
|
||||
deletePromises.push(this.deleteCiphersAdmin(this.cipherIds));
|
||||
@ -126,8 +112,7 @@ export class BulkDeleteDialogComponent {
|
||||
};
|
||||
|
||||
private async deleteCiphers(): Promise<any> {
|
||||
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
|
||||
const asAdmin = this.organization?.canEditAllCiphers(restrictProviderAccess);
|
||||
const asAdmin = this.organization?.canEditAllCiphers;
|
||||
if (this.permanent) {
|
||||
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
|
||||
} else {
|
||||
|
@ -32,7 +32,7 @@
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
[disabled]="!c.canEditItems(this.organization, this.restrictProviderAccess)"
|
||||
[disabled]="!c.canEditItems(this.organization)"
|
||||
/>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
|
@ -4,7 +4,6 @@ import { Component, Inject, OnDestroy } from "@angular/core";
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -25,7 +24,6 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
cipherService: CipherService,
|
||||
organizationSerivce: OrganizationService,
|
||||
logService: LogService,
|
||||
configService: ConfigService,
|
||||
accountService: AccountService,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: CollectionsDialogParams,
|
||||
@ -38,7 +36,6 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
cipherService,
|
||||
organizationSerivce,
|
||||
logService,
|
||||
configService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
@ -55,7 +52,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
if (!c.canEditItems(this.organization, this.restrictProviderAccess)) {
|
||||
if (!c.canEditItems(this.organization)) {
|
||||
return;
|
||||
}
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
|
@ -1161,7 +1161,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId);
|
||||
return organization.canEditAllCiphers(false);
|
||||
return organization.canEditAllCiphers;
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, OnInit, EventEmitter, OnDestroy } from "@angular/core";
|
||||
import { Component, EventEmitter, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@ -50,9 +48,7 @@ export class ViewComponent implements OnInit, OnDestroy {
|
||||
cipher: CipherView;
|
||||
onDeletedCipher = new EventEmitter<CipherView>();
|
||||
cipherTypeString: string;
|
||||
cipherEditUrl: string;
|
||||
organization: Organization;
|
||||
restrictProviderAccess = false;
|
||||
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
@ -67,7 +63,6 @@ export class ViewComponent implements OnInit, OnDestroy {
|
||||
private toastService: ToastService,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -79,9 +74,6 @@ export class ViewComponent implements OnInit, OnDestroy {
|
||||
if (this.cipher.organizationId) {
|
||||
this.organization = await this.organizationService.get(this.cipher.organizationId);
|
||||
}
|
||||
this.restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,7 +124,7 @@ export class ViewComponent implements OnInit, OnDestroy {
|
||||
* Helper method to delete cipher.
|
||||
*/
|
||||
protected async deleteCipher(): Promise<void> {
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
|
||||
const asAdmin = this.organization?.canEditAllCiphers;
|
||||
if (this.cipher.isDeleted) {
|
||||
await this.cipherService.deleteWithServer(this.cipher.id, asAdmin);
|
||||
} else {
|
||||
|
@ -83,7 +83,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (!this.organization.canEditAllCiphers) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
@ -93,10 +93,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
// Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin
|
||||
const firstCipherCheck = await super.loadCipher();
|
||||
|
||||
if (
|
||||
!this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
|
||||
firstCipherCheck != null
|
||||
) {
|
||||
if (!this.organization.canEditAllCiphers && firstCipherCheck != null) {
|
||||
return firstCipherCheck;
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -109,7 +106,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected encryptCipher(userId: UserId) {
|
||||
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (!this.organization.canEditAllCiphers) {
|
||||
return super.encryptCipher(userId);
|
||||
}
|
||||
|
||||
@ -117,7 +114,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected async deleteCipher() {
|
||||
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (!this.organization.canEditAllCiphers) {
|
||||
return super.deleteCipher();
|
||||
}
|
||||
return this.cipher.isDeleted
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -30,8 +27,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
||||
viewOnly = false;
|
||||
organization: Organization;
|
||||
|
||||
private restrictProviderAccess = false;
|
||||
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
i18nService: I18nService,
|
||||
@ -44,7 +39,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
||||
dialogService: DialogService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
@ -63,22 +57,16 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.restrictProviderAccess = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.RestrictProviderAccess),
|
||||
);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (
|
||||
this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
|
||||
this.showFixOldAttachments(attachment)
|
||||
) {
|
||||
if (this.organization.canEditAllCiphers && this.showFixOldAttachments(attachment)) {
|
||||
await super.reuploadCipherAttachment(attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (!this.organization.canEditAllCiphers) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -90,20 +78,18 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
||||
this.cipherDomain,
|
||||
file,
|
||||
userId,
|
||||
this.organization.canEditAllCiphers(this.restrictProviderAccess),
|
||||
this.organization.canEditAllCiphers,
|
||||
);
|
||||
}
|
||||
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (!this.organization.canEditAllCiphers) {
|
||||
return super.deleteCipherAttachment(attachmentId);
|
||||
}
|
||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return (
|
||||
attachment.key == null && this.organization.canEditAllCiphers(this.restrictProviderAccess)
|
||||
);
|
||||
return attachment.key == null && this.organization.canEditAllCiphers;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -37,7 +36,6 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
logService: LogService,
|
||||
configService: ConfigService,
|
||||
accountService: AccountService,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams,
|
||||
@ -50,7 +48,6 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
configService,
|
||||
accountService,
|
||||
dialogRef,
|
||||
params,
|
||||
@ -65,10 +62,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
|
||||
protected async loadCipher() {
|
||||
// if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
|
||||
if (
|
||||
!this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
|
||||
this.collectionIds.length !== 0
|
||||
) {
|
||||
if (!this.organization.canEditAllCiphers && this.collectionIds.length !== 0) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -90,10 +84,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected saveCollections() {
|
||||
if (
|
||||
this.organization.canEditAllCiphers(this.restrictProviderAccess) ||
|
||||
this.collectionIds.length === 0
|
||||
) {
|
||||
if (this.organization.canEditAllCiphers || this.collectionIds.length === 0) {
|
||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
||||
} else {
|
||||
|
@ -93,7 +93,7 @@
|
||||
</ng-container>
|
||||
|
||||
<bit-search
|
||||
*ngIf="restrictProviderAccessFlag && organization?.isProviderUser && !organization?.isMember"
|
||||
*ngIf="organization?.isProviderUser && !organization?.isMember"
|
||||
class="tw-grow"
|
||||
[ngModel]="searchText"
|
||||
(ngModelChange)="onSearchTextChanged($event)"
|
||||
|
@ -13,11 +13,11 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import {
|
||||
DialogService,
|
||||
SimpleDialogOptions,
|
||||
BreadcrumbsModule,
|
||||
DialogService,
|
||||
MenuModule,
|
||||
SearchModule,
|
||||
SimpleDialogOptions,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
@ -88,8 +88,6 @@ export class VaultHeaderComponent implements OnInit {
|
||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||
protected organizations$ = this.organizationService.organizations$;
|
||||
|
||||
protected restrictProviderAccessFlag = false;
|
||||
|
||||
/**
|
||||
* Whether the extension refresh feature flag is enabled.
|
||||
*/
|
||||
@ -108,9 +106,6 @@ export class VaultHeaderComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.restrictProviderAccessFlag = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.ExtensionRefresh,
|
||||
);
|
||||
@ -245,11 +240,7 @@ export class VaultHeaderComponent implements OnInit {
|
||||
}
|
||||
|
||||
get canCreateCipher(): boolean {
|
||||
if (
|
||||
this.organization?.isProviderUser &&
|
||||
this.restrictProviderAccessFlag &&
|
||||
!this.organization?.isMember
|
||||
) {
|
||||
if (this.organization?.isProviderUser && !this.organization?.isMember) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -70,7 +70,6 @@
|
||||
[viewingOrgVault]="true"
|
||||
[addAccessStatus]="addAccessStatus$ | async"
|
||||
[addAccessToggle]="showAddAccessToggle"
|
||||
[restrictProviderAccess]="restrictProviderAccessEnabled"
|
||||
>
|
||||
</app-vault-items>
|
||||
<ng-container *ngIf="!performingInitialLoad && isEmpty">
|
||||
|
@ -43,9 +43,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@ -172,17 +170,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected orgRevokedUsers: OrganizationUserUserDetailsResponse[];
|
||||
|
||||
private _restrictProviderAccessFlagEnabled: boolean;
|
||||
protected get restrictProviderAccessEnabled(): boolean {
|
||||
return this._restrictProviderAccessFlagEnabled;
|
||||
}
|
||||
|
||||
protected get hideVaultFilters(): boolean {
|
||||
return (
|
||||
this.restrictProviderAccessEnabled &&
|
||||
this.organization?.isProviderUser &&
|
||||
!this.organization?.isMember
|
||||
);
|
||||
return this.organization?.isProviderUser && !this.organization?.isMember;
|
||||
}
|
||||
|
||||
private searchText$ = new Subject<string>();
|
||||
@ -218,7 +207,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private apiService: ApiService,
|
||||
private collectionService: CollectionService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
protected configService: ConfigService,
|
||||
private toastService: ToastService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
@ -230,10 +218,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
: "trashCleanupWarning",
|
||||
);
|
||||
|
||||
this._restrictProviderAccessFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
|
||||
const filter$ = this.routedVaultFilterService.filter$;
|
||||
const organizationId$ = filter$.pipe(
|
||||
map((filter) => filter.organizationId),
|
||||
@ -325,7 +309,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
|
||||
map((collections) => {
|
||||
// Users that can edit all ciphers can implicitly add to / edit within any collection
|
||||
if (this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
|
||||
if (this.organization.canEditAllCiphers) {
|
||||
return collections;
|
||||
}
|
||||
// The user is only allowed to add/edit items to assigned collections that are not readonly
|
||||
@ -362,16 +346,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Restricted providers (who are not members) do not have access org cipher endpoint below
|
||||
// Return early to avoid 404 response
|
||||
if (
|
||||
this.restrictProviderAccessEnabled &&
|
||||
!organization.isMember &&
|
||||
organization.isProviderUser
|
||||
) {
|
||||
if (!organization.isMember && organization.isProviderUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If the user can edit all ciphers for the organization then fetch them ALL.
|
||||
if (organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
|
||||
if (organization.canEditAllCiphers) {
|
||||
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
||||
} else {
|
||||
// Otherwise, only fetch ciphers they have access to (includes unassigned for admins).
|
||||
@ -476,11 +456,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
]).pipe(
|
||||
map(([filter, collection, organization]) => {
|
||||
return (
|
||||
(filter.collectionId === Unassigned &&
|
||||
!organization.canEditUnassignedCiphers(this.restrictProviderAccessEnabled)) ||
|
||||
(!organization.canEditAllCiphers(this.restrictProviderAccessEnabled) &&
|
||||
collection != undefined &&
|
||||
!collection.node.assigned)
|
||||
(filter.collectionId === Unassigned && !organization.canEditUnassignedCiphers) ||
|
||||
(!organization.canEditAllCiphers && collection != undefined && !collection.node.assigned)
|
||||
);
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
@ -525,7 +502,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
const canEditCipher =
|
||||
organization.canEditAllCiphers(this.restrictProviderAccessEnabled) ||
|
||||
organization.canEditAllCiphers ||
|
||||
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
|
||||
|
||||
if (canEditCipher) {
|
||||
@ -758,15 +735,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.allCollectionsWithoutUnassigned$.pipe(
|
||||
map((c) => {
|
||||
return c.sort((a, b) => {
|
||||
if (
|
||||
a.canEditItems(this.organization, this.restrictProviderAccessEnabled) &&
|
||||
!b.canEditItems(this.organization, this.restrictProviderAccessEnabled)
|
||||
) {
|
||||
if (a.canEditItems(this.organization) && !b.canEditItems(this.organization)) {
|
||||
return -1;
|
||||
} else if (
|
||||
!a.canEditItems(this.organization, this.restrictProviderAccessEnabled) &&
|
||||
b.canEditItems(this.organization, this.restrictProviderAccessEnabled)
|
||||
) {
|
||||
} else if (!a.canEditItems(this.organization) && b.canEditItems(this.organization)) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name);
|
||||
@ -1001,9 +972,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const unassignedCiphers: string[] = [];
|
||||
|
||||
// If user has edit all Access no need to check for unassigned ciphers
|
||||
const canEditAll = this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled);
|
||||
|
||||
if (canEditAll) {
|
||||
if (this.organization.canEditAllCiphers) {
|
||||
ciphers.map((cipher) => {
|
||||
editAccessCiphers.push(cipher.id);
|
||||
});
|
||||
@ -1042,7 +1011,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async deleteCipher(c: CipherView): Promise<boolean> {
|
||||
if (!c.edit && !this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
|
||||
if (!c.edit && !this.organization.canEditAllCiphers) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
@ -1146,9 +1115,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const canDeleteCollections =
|
||||
collections == null || collections.every((c) => c.canDelete(organization));
|
||||
const canDeleteCiphers =
|
||||
ciphers == null ||
|
||||
ciphers.every((c) => c.edit) ||
|
||||
this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled);
|
||||
ciphers == null || ciphers.every((c) => c.edit) || this.organization.canEditAllCiphers;
|
||||
|
||||
if (!canDeleteCiphers || !canDeleteCollections) {
|
||||
this.showMissingPermissionsError();
|
||||
@ -1351,8 +1318,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipherWithServer(id: string, permanent: boolean, isUnassigned: boolean) {
|
||||
const asAdmin =
|
||||
this.organization?.canEditAllCiphers(this.restrictProviderAccessEnabled) || isUnassigned;
|
||||
const asAdmin = this.organization?.canEditAllCiphers || isUnassigned;
|
||||
return permanent
|
||||
? this.cipherService.deleteWithServer(id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(id, asAdmin);
|
||||
|
@ -4,8 +4,6 @@ import { firstValueFrom, map } from "rxjs";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -27,7 +25,6 @@ export class CollectionsComponent implements OnInit {
|
||||
collectionIds: string[];
|
||||
collections: CollectionView[] = [];
|
||||
organization: Organization;
|
||||
restrictProviderAccess: boolean;
|
||||
|
||||
protected cipherDomain: Cipher;
|
||||
|
||||
@ -38,15 +35,11 @@ export class CollectionsComponent implements OnInit {
|
||||
protected cipherService: CipherService,
|
||||
protected organizationService: OrganizationService,
|
||||
private logService: LogService,
|
||||
private configService: ConfigService,
|
||||
private accountService: AccountService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
await this.load();
|
||||
}
|
||||
|
||||
@ -76,7 +69,7 @@ export class CollectionsComponent implements OnInit {
|
||||
async submit(): Promise<boolean> {
|
||||
const selectedCollectionIds = this.collections
|
||||
.filter((c) => {
|
||||
if (this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
|
||||
if (this.organization.canEditAllCiphers) {
|
||||
return !!(c as any).checked;
|
||||
} else {
|
||||
return !!(c as any).checked && c.readOnly == null;
|
||||
|
@ -13,7 +13,6 @@ import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -92,8 +91,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||
private previousCipherId: string;
|
||||
|
||||
protected restrictProviderAccess = false;
|
||||
|
||||
get fido2CredentialCreationDateValue(): string {
|
||||
const dateCreated = this.i18nService.t("dateCreated");
|
||||
const creationDate = this.datePipe.transform(
|
||||
@ -182,10 +179,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
.pipe(
|
||||
@ -683,11 +676,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected saveCipher(cipher: Cipher) {
|
||||
const isNotClone = this.editMode && !this.cloneMode;
|
||||
let orgAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
|
||||
let orgAdmin = this.organization?.canEditAllCiphers;
|
||||
|
||||
// if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection
|
||||
if (!cipher.collectionIds) {
|
||||
orgAdmin = this.organization?.canEditUnassignedCiphers(this.restrictProviderAccess);
|
||||
orgAdmin = this.organization?.canEditUnassignedCiphers;
|
||||
}
|
||||
|
||||
return this.cipher.id == null
|
||||
@ -696,14 +689,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
|
||||
const asAdmin = this.organization?.canEditAllCiphers;
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
||||
}
|
||||
|
||||
protected restoreCipher() {
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
|
||||
const asAdmin = this.organization?.canEditAllCiphers;
|
||||
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
|
||||
}
|
||||
|
||||
|
@ -183,14 +183,7 @@ export class Organization {
|
||||
return this.isAdmin || this.permissions.editAnyCollection;
|
||||
}
|
||||
|
||||
canEditUnassignedCiphers(restrictProviderAccessFlagEnabled: boolean) {
|
||||
// Providers can access items until the restrictProviderAccess flag is enabled
|
||||
// After the flag is enabled and removed, this block will be deleted
|
||||
// so that they permanently lose access to items
|
||||
if (this.isProviderUser && !restrictProviderAccessFlagEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
get canEditUnassignedCiphers() {
|
||||
return (
|
||||
this.type === OrganizationUserType.Admin ||
|
||||
this.type === OrganizationUserType.Owner ||
|
||||
@ -198,14 +191,7 @@ export class Organization {
|
||||
);
|
||||
}
|
||||
|
||||
canEditAllCiphers(restrictProviderAccessFlagEnabled: boolean) {
|
||||
// Providers can access items until the restrictProviderAccess flag is enabled
|
||||
// After the flag is enabled and removed, this block will be deleted
|
||||
// so that they permanently lose access to items
|
||||
if (this.isProviderUser && !restrictProviderAccessFlagEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
get canEditAllCiphers() {
|
||||
// The allowAdminAccessToAllCollectionItems flag can restrict admins
|
||||
// Custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
|
||||
return (
|
||||
|
@ -12,7 +12,6 @@ export enum FeatureFlag {
|
||||
EnableDeleteProvider = "AC-1218-delete-provider",
|
||||
ExtensionRefresh = "extension-refresh",
|
||||
PersistPopupView = "persist-popup-view",
|
||||
RestrictProviderAccess = "restrict-provider-access",
|
||||
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
||||
UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection",
|
||||
EmailVerification = "email-verification",
|
||||
@ -59,7 +58,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
||||
[FeatureFlag.ExtensionRefresh]: FALSE,
|
||||
[FeatureFlag.PersistPopupView]: FALSE,
|
||||
[FeatureFlag.RestrictProviderAccess]: FALSE,
|
||||
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
||||
[FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE,
|
||||
[FeatureFlag.EmailVerification]: FALSE,
|
||||
|
@ -38,18 +38,14 @@ export class CollectionView implements View, ITreeNodeObject {
|
||||
}
|
||||
}
|
||||
|
||||
canEditItems(org: Organization, restrictProviderAccess: boolean): boolean {
|
||||
canEditItems(org: Organization): boolean {
|
||||
if (org != null && org.id !== this.organizationId) {
|
||||
throw new Error(
|
||||
"Id of the organization provided does not match the org id of the collection.",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
org?.canEditAllCiphers(restrictProviderAccess) ||
|
||||
this.manage ||
|
||||
(this.assigned && !this.readOnly)
|
||||
);
|
||||
return org?.canEditAllCiphers || this.manage || (this.assigned && !this.readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,12 +11,12 @@ import {
|
||||
} from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
shareReplay,
|
||||
Subject,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
tap,
|
||||
@ -27,8 +27,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/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 { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@ -170,7 +168,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
private organizationService: OrganizationService,
|
||||
private collectionService: CollectionService,
|
||||
private formBuilder: FormBuilder,
|
||||
@ -179,10 +176,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
);
|
||||
|
||||
this.activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
@ -193,7 +186,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
this.showOrgSelector = true;
|
||||
}
|
||||
|
||||
await this.initializeItems(this.selectedOrgId, restrictProviderAccess);
|
||||
await this.initializeItems(this.selectedOrgId);
|
||||
|
||||
if (this.selectedOrgId && this.selectedOrgId !== MY_VAULT_ID) {
|
||||
await this.handleOrganizationCiphers();
|
||||
@ -339,7 +332,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeItems(organizationId: OrganizationId, restrictProviderAccess: boolean) {
|
||||
private async initializeItems(organizationId: OrganizationId) {
|
||||
this.totalItemCount = this.params.ciphers.length;
|
||||
|
||||
// If organizationId is not present or organizationId is MyVault, then all ciphers are considered personal items
|
||||
@ -354,7 +347,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
const org = await this.organizationService.get(organizationId);
|
||||
this.orgName = org.name;
|
||||
|
||||
this.editableItems = org.canEditAllCiphers(restrictProviderAccess)
|
||||
this.editableItems = org.canEditAllCiphers
|
||||
? this.params.ciphers
|
||||
: this.params.ciphers.filter((c) => c.edit);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user