Merge branch 'main' into PM-5014-Migrate-download-license-component

This commit is contained in:
vinith-kovan 2024-05-10 12:22:16 +05:30 committed by GitHub
commit 7ee2052970
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 135 additions and 54 deletions

View File

@ -1,12 +1,16 @@
import { CommonModule } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import BrowserPopupUtils from "../browser-popup-utils";
@Component({ @Component({
selector: "app-pop-out", selector: "app-pop-out",
templateUrl: "pop-out.component.html", templateUrl: "pop-out.component.html",
standalone: true,
imports: [CommonModule, JslibModule],
}) })
export class PopOutComponent implements OnInit { export class PopOutComponent implements OnInit {
@Input() show = true; @Input() show = true;
@ -24,9 +28,7 @@ export class PopOutComponent implements OnInit {
} }
} }
expand() { async expand() {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await BrowserPopupUtils.openCurrentPagePopout(window);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserPopupUtils.openCurrentPagePopout(window);
} }
} }

View File

@ -41,6 +41,7 @@ import { AutofillComponent } from "../autofill/popup/settings/autofill.component
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
import { NotifcationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { NotifcationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
import { PremiumComponent } from "../billing/popup/settings/premium.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component";
import { PopOutComponent } from "../platform/popup/components/pop-out.component";
import { HeaderComponent } from "../platform/popup/header.component"; import { HeaderComponent } from "../platform/popup/header.component";
import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
@ -81,7 +82,6 @@ import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.c
import { AppRoutingModule } from "./app-routing.module"; import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component"; import { AppComponent } from "./app.component";
import { PopOutComponent } from "./components/pop-out.component";
import { UserVerificationComponent } from "./components/user-verification.component"; import { UserVerificationComponent } from "./components/user-verification.component";
import { ServicesModule } from "./services/services.module"; import { ServicesModule } from "./services/services.module";
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component"; import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
@ -118,6 +118,7 @@ import "../platform/popup/locales";
AccountComponent, AccountComponent,
ButtonModule, ButtonModule,
ExportScopeCalloutComponent, ExportScopeCalloutComponent,
PopOutComponent,
PopupPageComponent, PopupPageComponent,
PopupTabNavigationComponent, PopupTabNavigationComponent,
PopupFooterComponent, PopupFooterComponent,
@ -157,7 +158,6 @@ import "../platform/popup/locales";
GeneratorComponent, GeneratorComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordHistoryComponent, PasswordHistoryComponent,
PopOutComponent,
PremiumComponent, PremiumComponent,
RegisterComponent, RegisterComponent,
SendAddEditComponent, SendAddEditComponent,

View File

@ -58,7 +58,7 @@ export class VaultCipherRowComponent {
} }
protected editCollections() { protected editCollections() {
this.onEvent.emit({ type: "viewCollections", item: this.cipher }); this.onEvent.emit({ type: "viewCipherCollections", item: this.cipher });
} }
protected events() { protected events() {

View File

@ -63,7 +63,7 @@
</td> </td>
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right"> <td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
<button <button
*ngIf="canEditCollection || canDeleteCollection" *ngIf="canEditCollection || canDeleteCollection || canViewCollectionInfo"
[disabled]="disabled" [disabled]="disabled"
[bitMenuTriggerFor]="collectionOptions" [bitMenuTriggerFor]="collectionOptions"
size="small" size="small"
@ -73,14 +73,28 @@
appStopProp appStopProp
></button> ></button>
<bit-menu #collectionOptions> <bit-menu #collectionOptions>
<button *ngIf="canEditCollection" type="button" bitMenuItem (click)="edit()"> <ng-container *ngIf="canEditCollection">
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i> <button type="button" bitMenuItem (click)="edit(false)">
{{ "editInfo" | i18n }} <i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
</button> {{ "editInfo" | i18n }}
<button *ngIf="canEditCollection" type="button" bitMenuItem (click)="access()"> </button>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i> <button type="button" bitMenuItem (click)="access(false)">
{{ "access" | i18n }} <i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
</button> {{ "access" | i18n }}
</button>
</ng-container>
<ng-container
*ngIf="flexibleCollectionsV1Enabled && !canEditCollection && canViewCollectionInfo"
>
<button type="button" bitMenuItem (click)="edit(true)">
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "viewInfo" | i18n }}
</button>
<button type="button" bitMenuItem (click)="access(true)">
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "viewAccess" | i18n }}
</button>
</ng-container>
<button *ngIf="canDeleteCollection" type="button" bitMenuItem (click)="deleteCollection()"> <button *ngIf="canDeleteCollection" type="button" bitMenuItem (click)="deleteCollection()">
<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>

View File

@ -30,9 +30,11 @@ export class VaultCollectionRowComponent {
@Input() showGroups: boolean; @Input() showGroups: boolean;
@Input() canEditCollection: boolean; @Input() canEditCollection: boolean;
@Input() canDeleteCollection: boolean; @Input() canDeleteCollection: boolean;
@Input() canViewCollectionInfo: boolean;
@Input() organizations: Organization[]; @Input() organizations: Organization[];
@Input() groups: GroupView[]; @Input() groups: GroupView[];
@Input() showPermissionsColumn: boolean; @Input() showPermissionsColumn: boolean;
@Input() flexibleCollectionsV1Enabled: boolean;
@Output() onEvent = new EventEmitter<VaultItemEvent>(); @Output() onEvent = new EventEmitter<VaultItemEvent>();
@ -71,12 +73,12 @@ export class VaultCollectionRowComponent {
return ""; return "";
} }
protected edit() { protected edit(readonly: boolean) {
this.onEvent.next({ type: "editCollection", item: this.collection }); this.onEvent.next({ type: "editCollection", item: this.collection, readonly: readonly });
} }
protected access() { protected access(readonly: boolean) {
this.onEvent.next({ type: "viewCollectionAccess", item: this.collection }); this.onEvent.next({ type: "viewCollectionAccess", item: this.collection, readonly: readonly });
} }
protected deleteCollection() { protected deleteCollection() {

View File

@ -5,11 +5,11 @@ import { VaultItem } from "./vault-item";
export type VaultItemEvent = export type VaultItemEvent =
| { type: "viewAttachments"; item: CipherView } | { type: "viewAttachments"; item: CipherView }
| { type: "viewCollections"; item: CipherView } | { type: "viewCipherCollections"; item: CipherView }
| { type: "bulkEditCollectionAccess"; items: CollectionView[] } | { type: "bulkEditCollectionAccess"; items: CollectionView[] }
| { type: "viewCollectionAccess"; item: CollectionView } | { type: "viewCollectionAccess"; item: CollectionView; readonly: boolean }
| { type: "viewEvents"; item: CipherView } | { type: "viewEvents"; item: CipherView }
| { type: "editCollection"; item: CollectionView } | { type: "editCollection"; item: CollectionView; readonly: boolean }
| { type: "clone"; item: CipherView } | { type: "clone"; item: CipherView }
| { type: "restore"; items: CipherView[] } | { type: "restore"; items: CipherView[] }
| { type: "delete"; items: VaultItem[] } | { type: "delete"; items: VaultItem[] }

View File

@ -95,13 +95,15 @@
[groups]="allGroups" [groups]="allGroups"
[canDeleteCollection]="canDeleteCollection(item.collection)" [canDeleteCollection]="canDeleteCollection(item.collection)"
[canEditCollection]="canEditCollection(item.collection)" [canEditCollection]="canEditCollection(item.collection)"
[canViewCollectionInfo]="canViewCollectionInfo(item.collection)"
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
[checked]="selection.isSelected(item)" [checked]="selection.isSelected(item)"
(checkedToggled)="selection.toggle(item)" (checkedToggled)="selection.toggle(item)"
(onEvent)="event($event)" (onEvent)="event($event)"
></tr> ></tr>
<!-- <!--
addAccessStatus check here so ciphers do not show if user addAccessStatus check here so ciphers do not show if user
has filtered for collections with addAccess has filtered for collections with addAccess
--> -->
<tr <tr
*ngIf="item.cipher && (!addAccessToggle || (addAccessToggle && addAccessStatus !== 1))" *ngIf="item.cipher && (!addAccessToggle || (addAccessToggle && addAccessStatus !== 1))"

View File

@ -165,6 +165,11 @@ export class VaultItemsComponent {
return collection.canDelete(organization); return collection.canDelete(organization);
} }
protected canViewCollectionInfo(collection: CollectionView) {
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
return collection.canViewCollectionInfo(organization);
}
protected toggleAll() { protected toggleAll() {
this.isAllSelected this.isAllSelected
? this.selection.clear() ? this.selection.clear()

View File

@ -4,6 +4,7 @@ import { CollectionAccessDetailsResponse } from "@bitwarden/common/src/vault/mod
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { CollectionAccessSelectionView } from "../../../admin-console/organizations/core/views/collection-access-selection.view"; import { CollectionAccessSelectionView } from "../../../admin-console/organizations/core/views/collection-access-selection.view";
import { Unassigned } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
export class CollectionAdminView extends CollectionView { export class CollectionAdminView extends CollectionView {
groups: CollectionAccessSelectionView[] = []; groups: CollectionAccessSelectionView[] = [];
@ -89,4 +90,19 @@ export class CollectionAdminView extends CollectionView {
canEditGroupAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean { canEditGroupAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
return this.canEdit(org, flexibleCollectionsV1Enabled) || org.permissions.manageGroups; return this.canEdit(org, flexibleCollectionsV1Enabled) || org.permissions.manageGroups;
} }
/**
* Returns true if the user can view collection info and access in a read-only state from the Admin Console
*/
override canViewCollectionInfo(org: Organization | undefined): boolean {
if (this.isUnassignedCollection) {
return false;
}
return this.manage || org?.isAdmin || org?.permissions.editAnyCollection;
}
get isUnassignedCollection() {
return this.id === Unassigned;
}
} }

View File

@ -434,7 +434,7 @@ export class VaultComponent implements OnInit, OnDestroy {
try { try {
if (event.type === "viewAttachments") { if (event.type === "viewAttachments") {
await this.editCipherAttachments(event.item); await this.editCipherAttachments(event.item);
} else if (event.type === "viewCollections") { } else if (event.type === "viewCipherCollections") {
await this.editCipherCollections(event.item); await this.editCipherCollections(event.item);
} else if (event.type === "clone") { } else if (event.type === "clone") {
await this.cloneCipher(event.item); await this.cloneCipher(event.item);

View File

@ -37,24 +37,44 @@
aria-haspopup="true" aria-haspopup="true"
></button> ></button>
<bit-menu #editCollectionMenu> <bit-menu #editCollectionMenu>
<button <ng-container *ngIf="canEditCollection">
type="button" <button
*ngIf="canEditCollection" type="button"
bitMenuItem bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info)" (click)="editCollection(CollectionDialogTabType.Info, false)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access, false)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
</ng-container>
<ng-container
*ngIf="flexibleCollectionsV1Enabled && !canEditCollection && canViewCollectionInfo"
> >
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i> <button
{{ "editInfo" | i18n }} type="button"
</button> bitMenuItem
<button (click)="editCollection(CollectionDialogTabType.Info, true)"
type="button" >
*ngIf="canEditCollection" <i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
bitMenuItem {{ "viewInfo" | i18n }}
(click)="editCollection(CollectionDialogTabType.Access)" </button>
> <button
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i> type="button"
{{ "access" | i18n }} bitMenuItem
</button> (click)="editCollection(CollectionDialogTabType.Access, true)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "viewAccess" | i18n }}
</button>
</ng-container>
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()"> <button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
<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>

View File

@ -53,7 +53,10 @@ export class VaultHeaderComponent implements OnInit {
@Output() onAddCollection = new EventEmitter<void>(); @Output() onAddCollection = new EventEmitter<void>();
/** Emits an event when the edit collection button is clicked in the header */ /** Emits an event when the edit collection button is clicked in the header */
@Output() onEditCollection = new EventEmitter<{ tab: CollectionDialogTabType }>(); @Output() onEditCollection = new EventEmitter<{
tab: CollectionDialogTabType;
readonly: boolean;
}>();
/** Emits an event when the delete collection button is clicked in the header */ /** Emits an event when the delete collection button is clicked in the header */
@Output() onDeleteCollection = new EventEmitter<void>(); @Output() onDeleteCollection = new EventEmitter<void>();
@ -64,7 +67,7 @@ export class VaultHeaderComponent implements OnInit {
protected CollectionDialogTabType = CollectionDialogTabType; protected CollectionDialogTabType = CollectionDialogTabType;
protected organizations$ = this.organizationService.organizations$; protected organizations$ = this.organizationService.organizations$;
private flexibleCollectionsV1Enabled = false; protected flexibleCollectionsV1Enabled = false;
private restrictProviderAccessFlag = false; private restrictProviderAccessFlag = false;
constructor( constructor(
@ -193,8 +196,8 @@ export class VaultHeaderComponent implements OnInit {
this.onAddCollection.emit(); this.onAddCollection.emit();
} }
async editCollection(tab: CollectionDialogTabType): Promise<void> { async editCollection(tab: CollectionDialogTabType, readonly: boolean): Promise<void> {
this.onEditCollection.emit({ tab }); this.onEditCollection.emit({ tab, readonly });
} }
get canDeleteCollection(): boolean { get canDeleteCollection(): boolean {
@ -207,6 +210,10 @@ export class VaultHeaderComponent implements OnInit {
return this.collection.node.canDelete(this.organization); return this.collection.node.canDelete(this.organization);
} }
get canViewCollectionInfo(): boolean {
return this.collection.node.canViewCollectionInfo(this.organization);
}
get canCreateCollection(): boolean { get canCreateCollection(): boolean {
return this.organization?.canCreateNewCollections; return this.organization?.canCreateNewCollections;
} }

View File

@ -6,7 +6,7 @@
[searchText]="currentSearchText$ | async" [searchText]="currentSearchText$ | async"
(onAddCipher)="addCipher()" (onAddCipher)="addCipher()"
(onAddCollection)="addCollection()" (onAddCollection)="addCollection()"
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)" (onEditCollection)="editCollection(selectedCollection.node, $event.tab, $event.readonly)"
(onDeleteCollection)="deleteCollection(selectedCollection.node)" (onDeleteCollection)="deleteCollection(selectedCollection.node)"
(searchTextChanged)="filterSearchText($event)" (searchTextChanged)="filterSearchText($event)"
></app-org-vault-header> ></app-org-vault-header>

View File

@ -736,7 +736,7 @@ export class VaultComponent implements OnInit, OnDestroy {
try { try {
if (event.type === "viewAttachments") { if (event.type === "viewAttachments") {
await this.editCipherAttachments(event.item); await this.editCipherAttachments(event.item);
} else if (event.type === "viewCollections") { } else if (event.type === "viewCipherCollections") {
await this.editCipherCollections(event.item); await this.editCipherCollections(event.item);
} else if (event.type === "clone") { } else if (event.type === "clone") {
await this.cloneCipher(event.item); await this.cloneCipher(event.item);
@ -761,9 +761,9 @@ export class VaultComponent implements OnInit, OnDestroy {
} else if (event.type === "copyField") { } else if (event.type === "copyField") {
await this.copy(event.item, event.field); await this.copy(event.item, event.field);
} else if (event.type === "editCollection") { } else if (event.type === "editCollection") {
await this.editCollection(event.item, CollectionDialogTabType.Info); await this.editCollection(event.item, CollectionDialogTabType.Info, event.readonly);
} else if (event.type === "viewCollectionAccess") { } else if (event.type === "viewCollectionAccess") {
await this.editCollection(event.item, CollectionDialogTabType.Access); await this.editCollection(event.item, CollectionDialogTabType.Access, event.readonly);
} else if (event.type === "bulkEditCollectionAccess") { } else if (event.type === "bulkEditCollectionAccess") {
await this.bulkEditCollectionAccess(event.items); await this.bulkEditCollectionAccess(event.items);
} else if (event.type === "assignToCollections") { } else if (event.type === "assignToCollections") {
@ -1190,7 +1190,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async editCollection( async editCollection(
c: CollectionView, c: CollectionView,
tab: CollectionDialogTabType, tab: CollectionDialogTabType,
readonly: boolean = false, readonly: boolean,
): Promise<void> { ): Promise<void> {
const dialog = openCollectionDialog(this.dialogService, { const dialog = openCollectionDialog(this.dialogService, {
data: { data: {

View File

@ -8081,5 +8081,11 @@
}, },
"manageBillingFromProviderPortalMessage": { "manageBillingFromProviderPortalMessage": {
"message": "Manage billing from the Provider Portal" "message": "Manage billing from the Provider Portal"
},
"viewInfo": {
"message": "View info"
},
"viewAccess": {
"message": "View access"
} }
} }

View File

@ -87,6 +87,13 @@ export class CollectionView implements View, ITreeNodeObject {
: org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections; : org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections;
} }
/**
* Returns true if the user can view collection info and access in a read-only state from the individual vault
*/
canViewCollectionInfo(org: Organization | undefined): boolean {
return false;
}
static fromJSON(obj: Jsonify<CollectionView>) { static fromJSON(obj: Jsonify<CollectionView>) {
return Object.assign(new CollectionView(new Collection()), obj); return Object.assign(new CollectionView(new Collection()), obj);
} }