From 84aa7fffd72aa555941d26abd9dafa071d3e1ce7 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Mon, 27 Feb 2023 09:43:06 -0600 Subject: [PATCH] [SM-474] Service Account - People Tab (#4689) * Init service layer changes * refactor service to inherit abstract * refactor access-selector component * update access selector in projects * add service accounts access selector * update i18n * fix delete action; use useExisting in providers * update static permissions * service account people should be readwrite on creation * use setter instead of observable input * remove warning callout * remove abstract service * truncate name in table * remove extra comments * use map instead of forEach * refactor view factories * update SA people copy * map list responses --------- Co-authored-by: William Martin --- apps/web/src/locales/en/messages.json | 3 + .../models/view/access-policy.view.ts | 23 ++ .../view/project-access-policies.view.ts | 11 - .../project/project-access.component.html | 23 -- .../project/project-access.component.ts | 54 --- .../project/project-people.component.html | 24 +- .../project/project-people.component.ts | 103 +++++- .../project-service-accounts.component.html | 24 +- .../project-service-accounts.component.ts | 78 ++++- .../projects/projects.module.ts | 2 - .../service-account-people.component.html | 15 + .../service-account-people.component.ts | 107 ++++++ .../service-accounts-routing.module.ts | 5 + .../service-accounts.module.ts | 2 + .../access-policies/access-policy.service.ts | 319 +++++++++++------ .../access-selector.component.html | 39 ++- .../access-selector.component.ts | 321 ++++++------------ .../responses/access-policy.response.ts | 26 ++ .../project-access-policies.response.ts | 15 +- ...rvice-accounts-access-policies.response.ts | 23 ++ 20 files changed, 760 insertions(+), 457 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-access-policies.view.ts delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.html delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-accounts-access-policies.response.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b3ee19154fc..b06e0144e16 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6362,6 +6362,9 @@ "projectEmptyServiceAccountAccessPolicies": { "message": "Add service accounts to grant access" }, + "serviceAccountPeopleDescription": { + "message": "Grant groups or people access to this service account." + }, "canWrite": { "message": "Can write" }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts index 8af275e63f1..e4fc1fb9426 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts @@ -12,14 +12,37 @@ export class UserProjectAccessPolicyView extends BaseAccessPolicyView { grantedProjectId: string; } +export class UserServiceAccountAccessPolicyView extends BaseAccessPolicyView { + organizationUserId: string; + organizationUserName: string; + grantedServiceAccountId: string; +} + export class GroupProjectAccessPolicyView extends BaseAccessPolicyView { groupId: string; groupName: string; grantedProjectId: string; } +export class GroupServiceAccountAccessPolicyView extends BaseAccessPolicyView { + groupId: string; + groupName: string; + grantedServiceAccountId: string; +} + export class ServiceAccountProjectAccessPolicyView extends BaseAccessPolicyView { serviceAccountId: string; serviceAccountName: string; grantedProjectId: string; } + +export class ProjectAccessPoliciesView { + userAccessPolicies: UserProjectAccessPolicyView[]; + groupAccessPolicies: GroupProjectAccessPolicyView[]; + serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[]; +} + +export class ServiceAccountAccessPoliciesView { + userAccessPolicies: UserServiceAccountAccessPolicyView[]; + groupAccessPolicies: GroupServiceAccountAccessPolicyView[]; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-access-policies.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-access-policies.view.ts deleted file mode 100644 index 5f05b595b63..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-access-policies.view.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - GroupProjectAccessPolicyView, - ServiceAccountProjectAccessPolicyView, - UserProjectAccessPolicyView, -} from "./access-policy.view"; - -export class ProjectAccessPoliciesView { - userAccessPolicies: UserProjectAccessPolicyView[]; - groupAccessPolicies: GroupProjectAccessPolicyView[]; - serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[]; -} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.html deleted file mode 100644 index 4ba5fed4020..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.html +++ /dev/null @@ -1,23 +0,0 @@ - -
-

- {{ description }} -

- - -
-
- - -
- -
-
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.ts deleted file mode 100644 index ffe51b8000a..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-access.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component, Input, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { combineLatestWith, Observable, share, startWith, switchMap } from "rxjs"; - -import { PotentialGranteeView } from "../../models/view/potential-grantee.view"; -import { ProjectAccessPoliciesView } from "../../models/view/project-access-policies.view"; -import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; - -@Component({ - selector: "sm-project-access", - templateUrl: "./project-access.component.html", -}) -export class ProjectAccessComponent implements OnInit { - @Input() accessType: "projectPeople" | "projectServiceAccounts"; - @Input() description: string; - @Input() label: string; - @Input() hint: string; - @Input() columnTitle: string; - @Input() emptyMessage: string; - - protected projectAccessPolicies$: Observable; - protected potentialGrantees$: Observable; - - constructor(private route: ActivatedRoute, private accessPolicyService: AccessPolicyService) {} - - ngOnInit(): void { - this.projectAccessPolicies$ = this.accessPolicyService.projectAccessPolicies$.pipe( - startWith(null), - combineLatestWith(this.route.params), - switchMap(([_, params]) => { - return this.accessPolicyService.getProjectAccessPolicies( - params.organizationId, - params.projectId - ); - }), - share() - ); - - this.potentialGrantees$ = this.accessPolicyService.projectAccessPolicies$.pipe( - startWith(null), - combineLatestWith(this.route.params), - switchMap(async ([_, params]) => { - if (this.accessType == "projectPeople") { - return await this.accessPolicyService.getPeoplePotentialGrantees(params.organizationId); - } else { - return await this.accessPolicyService.getServiceAccountsPotentialGrantees( - params.organizationId - ); - } - }), - share() - ); - } -} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html index f3a39f41540..124eafb8de0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html @@ -1,9 +1,15 @@ - - +
+

+ {{ "projectPeopleDescription" | i18n }} +

+ + +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 34492727411..d1ee0e3ce10 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -1,7 +1,106 @@ -import { Component } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { map, Observable, startWith, Subject, switchMap, takeUntil } from "rxjs"; + +import { SelectItemView } from "@bitwarden/components"; + +import { + GroupProjectAccessPolicyView, + ProjectAccessPoliciesView, + UserProjectAccessPolicyView, +} from "../../models/view/access-policy.view"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; +import { + AccessSelectorComponent, + AccessSelectorRowView, +} from "../../shared/access-policies/access-selector.component"; @Component({ selector: "sm-project-people", templateUrl: "./project-people.component.html", }) -export class ProjectPeopleComponent {} +export class ProjectPeopleComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + private organizationId: string; + private projectId: string; + + protected rows$: Observable = + this.accessPolicyService.projectAccessPolicyChanges$.pipe( + startWith(null), + switchMap(() => + this.accessPolicyService.getProjectAccessPolicies(this.organizationId, this.projectId) + ), + map((policies) => { + const rows: AccessSelectorRowView[] = []; + policies.userAccessPolicies.forEach((policy) => { + rows.push({ + type: "user", + name: policy.organizationUserName, + granteeId: policy.organizationUserId, + accessPolicyId: policy.id, + read: policy.read, + write: policy.write, + icon: AccessSelectorComponent.userIcon, + }); + }); + + policies.groupAccessPolicies.forEach((policy) => { + rows.push({ + type: "group", + name: policy.groupName, + granteeId: policy.groupId, + accessPolicyId: policy.id, + read: policy.read, + write: policy.write, + icon: AccessSelectorComponent.groupIcon, + }); + }); + return rows; + }) + ); + + protected handleCreateAccessPolicies(selected: SelectItemView[]) { + const projectAccessPoliciesView = new ProjectAccessPoliciesView(); + projectAccessPoliciesView.userAccessPolicies = selected + .filter((selection) => AccessSelectorComponent.getAccessItemType(selection) === "user") + .map((filtered) => { + const view = new UserProjectAccessPolicyView(); + view.grantedProjectId = this.projectId; + view.organizationUserId = filtered.id; + view.read = true; + view.write = false; + return view; + }); + + projectAccessPoliciesView.groupAccessPolicies = selected + .filter((selection) => AccessSelectorComponent.getAccessItemType(selection) === "group") + .map((filtered) => { + const view = new GroupProjectAccessPolicyView(); + view.grantedProjectId = this.projectId; + view.groupId = filtered.id; + view.read = true; + view.write = false; + return view; + }); + + return this.accessPolicyService.createProjectAccessPolicies( + this.organizationId, + this.projectId, + projectAccessPoliciesView + ); + } + + constructor(private route: ActivatedRoute, private accessPolicyService: AccessPolicyService) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.organizationId = params.organizationId; + this.projectId = params.projectId; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html index 3f82e6d8853..12bdd05316a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html @@ -1,9 +1,15 @@ - - +
+

+ {{ "projectServiceAccountsDescription" | i18n }} +

+ + +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts index 194e5f2d224..e442ef4b168 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts @@ -1,7 +1,81 @@ -import { Component } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { map, Observable, startWith, Subject, switchMap, takeUntil } from "rxjs"; + +import { SelectItemView } from "@bitwarden/components"; + +import { + ProjectAccessPoliciesView, + ServiceAccountProjectAccessPolicyView, +} from "../../models/view/access-policy.view"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; +import { + AccessSelectorComponent, + AccessSelectorRowView, +} from "../../shared/access-policies/access-selector.component"; @Component({ selector: "sm-project-service-accounts", templateUrl: "./project-service-accounts.component.html", }) -export class ProjectServiceAccountsComponent {} +export class ProjectServiceAccountsComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + private organizationId: string; + private projectId: string; + + protected rows$: Observable = + this.accessPolicyService.projectAccessPolicyChanges$.pipe( + startWith(null), + switchMap(() => + this.accessPolicyService.getProjectAccessPolicies(this.organizationId, this.projectId) + ), + map((policies) => + policies.serviceAccountAccessPolicies.map((policy) => ({ + type: "serviceAccount", + name: policy.serviceAccountName, + granteeId: policy.serviceAccountId, + accessPolicyId: policy.id, + read: policy.read, + write: policy.write, + icon: AccessSelectorComponent.serviceAccountIcon, + static: true, + })) + ) + ); + + protected handleCreateAccessPolicies(selected: SelectItemView[]) { + const projectAccessPoliciesView = new ProjectAccessPoliciesView(); + projectAccessPoliciesView.serviceAccountAccessPolicies = selected + .filter( + (selection) => AccessSelectorComponent.getAccessItemType(selection) === "serviceAccount" + ) + .map((filtered) => { + const view = new ServiceAccountProjectAccessPolicyView(); + view.grantedProjectId = this.projectId; + view.serviceAccountId = filtered.id; + view.read = true; + view.write = false; + return view; + }); + + return this.accessPolicyService.createProjectAccessPolicies( + this.organizationId, + this.projectId, + projectAccessPoliciesView + ); + } + + constructor(private route: ActivatedRoute, private accessPolicyService: AccessPolicyService) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.organizationId = params.organizationId; + this.projectId = params.projectId; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts index fe06359e0cd..de224a4a4a1 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects.module.ts @@ -6,7 +6,6 @@ import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component"; import { ProjectDialogComponent } from "./dialog/project-dialog.component"; -import { ProjectAccessComponent } from "./project/project-access.component"; import { ProjectPeopleComponent } from "./project/project-people.component"; import { ProjectSecretsComponent } from "./project/project-secrets.component"; import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component"; @@ -18,7 +17,6 @@ import { ProjectsComponent } from "./projects/projects.component"; imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule], declarations: [ ProjectsComponent, - ProjectAccessComponent, ProjectDialogComponent, ProjectDeleteDialogComponent, ProjectPeopleComponent, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html new file mode 100644 index 00000000000..2eb41bce214 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html @@ -0,0 +1,15 @@ +
+

+ {{ "serviceAccountPeopleDescription" | i18n }} +

+ + +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts new file mode 100644 index 00000000000..dbc957ebc5a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -0,0 +1,107 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { combineLatestWith, map, Observable, startWith, Subject, switchMap, takeUntil } from "rxjs"; + +import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; + +import { + GroupServiceAccountAccessPolicyView, + ServiceAccountAccessPoliciesView, + UserServiceAccountAccessPolicyView, +} from "../../models/view/access-policy.view"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; +import { + AccessSelectorComponent, + AccessSelectorRowView, +} from "../../shared/access-policies/access-selector.component"; + +@Component({ + selector: "sm-service-account-people", + templateUrl: "./service-account-people.component.html", +}) +export class ServiceAccountPeopleComponent { + private destroy$ = new Subject(); + private serviceAccountId: string; + + protected rows$: Observable = + this.accessPolicyService.serviceAccountAccessPolicyChanges$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(([_, params]) => + this.accessPolicyService.getServiceAccountAccessPolicies(params.serviceAccountId) + ), + map((policies) => { + const rows: AccessSelectorRowView[] = []; + policies.userAccessPolicies.forEach((policy) => { + rows.push({ + type: "user", + name: policy.organizationUserName, + granteeId: policy.organizationUserId, + accessPolicyId: policy.id, + read: policy.read, + write: policy.write, + icon: AccessSelectorComponent.userIcon, + static: true, + }); + }); + + policies.groupAccessPolicies.forEach((policy) => { + rows.push({ + type: "group", + name: policy.groupName, + granteeId: policy.groupId, + accessPolicyId: policy.id, + read: policy.read, + write: policy.write, + icon: AccessSelectorComponent.groupIcon, + static: true, + }); + }); + + return rows; + }) + ); + + protected handleCreateAccessPolicies(selected: SelectItemView[]) { + const serviceAccountAccessPoliciesView = new ServiceAccountAccessPoliciesView(); + serviceAccountAccessPoliciesView.userAccessPolicies = selected + .filter((selection) => AccessSelectorComponent.getAccessItemType(selection) === "user") + .map((filtered) => { + const view = new UserServiceAccountAccessPolicyView(); + view.grantedServiceAccountId = this.serviceAccountId; + view.organizationUserId = filtered.id; + view.read = true; + view.write = true; + return view; + }); + + serviceAccountAccessPoliciesView.groupAccessPolicies = selected + .filter((selection) => AccessSelectorComponent.getAccessItemType(selection) === "group") + .map((filtered) => { + const view = new GroupServiceAccountAccessPolicyView(); + view.grantedServiceAccountId = this.serviceAccountId; + view.groupId = filtered.id; + view.read = true; + view.write = true; + return view; + }); + + return this.accessPolicyService.createServiceAccountAccessPolicies( + this.serviceAccountId, + serviceAccountAccessPoliciesView + ); + } + + constructor(private route: ActivatedRoute, private accessPolicyService: AccessPolicyService) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.serviceAccountId = params.serviceAccountId; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts index 1db2110fecb..1fdca60605d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AccessTokenComponent } from "./access/access-tokens.component"; +import { ServiceAccountPeopleComponent } from "./people/service-account-people.component"; import { ServiceAccountComponent } from "./service-account.component"; import { ServiceAccountsComponent } from "./service-accounts.component"; @@ -23,6 +24,10 @@ const routes: Routes = [ path: "access", component: AccessTokenComponent, }, + { + path: "people", + component: ServiceAccountPeopleComponent, + }, ], }, ]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts index c9a3ae721a9..e75bf2dc3fc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts @@ -10,6 +10,7 @@ import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token- import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component"; import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component"; import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component"; +import { ServiceAccountPeopleComponent } from "./people/service-account-people.component"; import { ServiceAccountComponent } from "./service-account.component"; import { ServiceAccountsListComponent } from "./service-accounts-list.component"; import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module"; @@ -27,6 +28,7 @@ import { ServiceAccountsComponent } from "./service-accounts.component"; ServiceAccountDialogComponent, ServiceAccountsComponent, ServiceAccountsListComponent, + ServiceAccountPeopleComponent, ], providers: [], }) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts index a9b7f876c2c..c63abc1b143 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts @@ -11,34 +11,51 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { BaseAccessPolicyView, GroupProjectAccessPolicyView, + GroupServiceAccountAccessPolicyView, + ProjectAccessPoliciesView, + ServiceAccountAccessPoliciesView, ServiceAccountProjectAccessPolicyView, UserProjectAccessPolicyView, + UserServiceAccountAccessPolicyView, } from "../../models/view/access-policy.view"; import { PotentialGranteeView } from "../../models/view/potential-grantee.view"; -import { ProjectAccessPoliciesView } from "../../models/view/project-access-policies.view"; +import { AccessPoliciesCreateRequest } from "../../shared/access-policies/models/requests/access-policies-create.request"; +import { ProjectAccessPoliciesResponse } from "../../shared/access-policies/models/responses/project-access-policies.response"; +import { ServiceAccountAccessPoliciesResponse } from "../../shared/access-policies/models/responses/service-accounts-access-policies.response"; -import { AccessPoliciesCreateRequest } from "./models/requests/access-policies-create.request"; import { AccessPolicyUpdateRequest } from "./models/requests/access-policy-update.request"; import { AccessPolicyRequest } from "./models/requests/access-policy.request"; import { + GroupServiceAccountAccessPolicyResponse, + UserServiceAccountAccessPolicyResponse, GroupProjectAccessPolicyResponse, ServiceAccountProjectAccessPolicyResponse, UserProjectAccessPolicyResponse, } from "./models/responses/access-policy.response"; import { PotentialGranteeResponse } from "./models/responses/potential-grantee.response"; -import { ProjectAccessPoliciesResponse } from "./models/responses/project-access-policies.response"; @Injectable({ providedIn: "root", }) export class AccessPolicyService { - protected _projectAccessPolicies = new Subject(); - projectAccessPolicies$ = this._projectAccessPolicies.asObservable(); + private _projectAccessPolicyChanges$ = new Subject(); + private _serviceAccountAccessPolicyChanges$ = new Subject(); + + /** + * Emits when a project access policy is created or deleted. + */ + readonly projectAccessPolicyChanges$ = this._projectAccessPolicyChanges$.asObservable(); + + /** + * Emits when a service account access policy is created or deleted. + */ + readonly serviceAccountAccessPolicyChanges$ = + this._serviceAccountAccessPolicyChanges$.asObservable(); constructor( private cryptoService: CryptoService, - private apiService: ApiService, - private encryptService: EncryptService + protected apiService: ApiService, + protected encryptService: EncryptService ) {} async getProjectAccessPolicies( @@ -57,46 +74,19 @@ export class AccessPolicyService { return await this.createProjectAccessPoliciesView(organizationId, results); } - async getPeoplePotentialGrantees(organizationId: string) { + async getServiceAccountAccessPolicies( + serviceAccountId: string + ): Promise { const r = await this.apiService.send( "GET", - "/organizations/" + organizationId + "/access-policies/people/potential-grantees", + "/service-accounts/" + serviceAccountId + "/access-policies", null, true, true ); - const results = new ListResponse(r, PotentialGranteeResponse); - return await this.createPotentialGranteeViews(organizationId, results.data); - } - async getServiceAccountsPotentialGrantees(organizationId: string) { - const r = await this.apiService.send( - "GET", - "/organizations/" + organizationId + "/access-policies/service-accounts/potential-grantees", - null, - true, - true - ); - const results = new ListResponse(r, PotentialGranteeResponse); - return await this.createPotentialGranteeViews(organizationId, results.data); - } - - async deleteAccessPolicy(accessPolicyId: string): Promise { - await this.apiService.send("DELETE", "/access-policies/" + accessPolicyId, null, true, false); - this._projectAccessPolicies.next(null); - } - - async updateAccessPolicy(baseAccessPolicyView: BaseAccessPolicyView): Promise { - const payload = new AccessPolicyUpdateRequest(); - payload.read = baseAccessPolicyView.read; - payload.write = baseAccessPolicyView.write; - await this.apiService.send( - "PUT", - "/access-policies/" + baseAccessPolicyView.id, - payload, - true, - true - ); + const results = new ServiceAccountAccessPoliciesResponse(r); + return await this.createServiceAccountAccessPoliciesView(results); } async createProjectAccessPolicies( @@ -114,12 +104,68 @@ export class AccessPolicyService { ); const results = new ProjectAccessPoliciesResponse(r); const view = await this.createProjectAccessPoliciesView(organizationId, results); - this._projectAccessPolicies.next(view); + this._projectAccessPolicyChanges$.next(view); return view; } - private async getOrganizationKey(organizationId: string): Promise { - return await this.cryptoService.getOrgKey(organizationId); + async createServiceAccountAccessPolicies( + serviceAccountId: string, + serviceAccountAccessPoliciesView: ServiceAccountAccessPoliciesView + ): Promise { + const request = this.getServiceAccountAccessPoliciesCreateRequest( + serviceAccountAccessPoliciesView + ); + const r = await this.apiService.send( + "POST", + "/service-accounts/" + serviceAccountId + "/access-policies", + request, + true, + true + ); + const results = new ServiceAccountAccessPoliciesResponse(r); + const view = await this.createServiceAccountAccessPoliciesView(results); + this._serviceAccountAccessPolicyChanges$.next(view); + return view; + } + + async deleteAccessPolicy(accessPolicyId: string): Promise { + await this.apiService.send("DELETE", "/access-policies/" + accessPolicyId, null, true, false); + this._projectAccessPolicyChanges$.next(null); + this._serviceAccountAccessPolicyChanges$.next(null); + } + + async updateAccessPolicy(baseAccessPolicyView: BaseAccessPolicyView): Promise { + const payload = new AccessPolicyUpdateRequest(); + payload.read = baseAccessPolicyView.read; + payload.write = baseAccessPolicyView.write; + await this.apiService.send( + "PUT", + "/access-policies/" + baseAccessPolicyView.id, + payload, + true, + true + ); + } + + private async createProjectAccessPoliciesView( + organizationId: string, + projectAccessPoliciesResponse: ProjectAccessPoliciesResponse + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + const view = new ProjectAccessPoliciesView(); + + view.userAccessPolicies = projectAccessPoliciesResponse.userAccessPolicies.map((ap) => { + return this.createUserProjectAccessPolicyView(ap); + }); + view.groupAccessPolicies = projectAccessPoliciesResponse.groupAccessPolicies.map((ap) => { + return this.createGroupProjectAccessPolicyView(ap); + }); + view.serviceAccountAccessPolicies = await Promise.all( + projectAccessPoliciesResponse.serviceAccountAccessPolicies.map(async (ap) => { + return await this.createServiceAccountProjectAccessPolicyView(orgKey, ap); + }) + ); + return view; } private getAccessPoliciesCreateRequest( @@ -152,11 +198,137 @@ export class AccessPolicyService { return createRequest; } - private getAccessPolicyRequest( + private createUserProjectAccessPolicyView( + response: UserProjectAccessPolicyResponse + ): UserProjectAccessPolicyView { + return { + ...this.createBaseAccessPolicyView(response), + grantedProjectId: response.grantedProjectId, + organizationUserId: response.organizationUserId, + organizationUserName: response.organizationUserName, + }; + } + + private createGroupProjectAccessPolicyView( + response: GroupProjectAccessPolicyResponse + ): GroupProjectAccessPolicyView { + return { + ...this.createBaseAccessPolicyView(response), + grantedProjectId: response.grantedProjectId, + groupId: response.groupId, + groupName: response.groupName, + }; + } + + private async createServiceAccountProjectAccessPolicyView( + organizationKey: SymmetricCryptoKey, + response: ServiceAccountProjectAccessPolicyResponse + ): Promise { + return { + ...this.createBaseAccessPolicyView(response), + grantedProjectId: response.grantedProjectId, + serviceAccountId: response.serviceAccountId, + serviceAccountName: await this.encryptService.decryptToUtf8( + new EncString(response.serviceAccountName), + organizationKey + ), + }; + } + + private getServiceAccountAccessPoliciesCreateRequest( + serviceAccountAccessPoliciesView: ServiceAccountAccessPoliciesView + ): AccessPoliciesCreateRequest { + const createRequest = new AccessPoliciesCreateRequest(); + + if (serviceAccountAccessPoliciesView.userAccessPolicies?.length > 0) { + createRequest.userAccessPolicyRequests = + serviceAccountAccessPoliciesView.userAccessPolicies.map((ap) => { + return this.getAccessPolicyRequest(ap.organizationUserId, ap); + }); + } + + if (serviceAccountAccessPoliciesView.groupAccessPolicies?.length > 0) { + createRequest.groupAccessPolicyRequests = + serviceAccountAccessPoliciesView.groupAccessPolicies.map((ap) => { + return this.getAccessPolicyRequest(ap.groupId, ap); + }); + } + + return createRequest; + } + + private async createServiceAccountAccessPoliciesView( + serviceAccountAccessPoliciesResponse: ServiceAccountAccessPoliciesResponse + ): Promise { + const view = new ServiceAccountAccessPoliciesView(); + view.userAccessPolicies = serviceAccountAccessPoliciesResponse.userAccessPolicies.map((ap) => { + return this.createUserServiceAccountAccessPolicyView(ap); + }); + view.groupAccessPolicies = serviceAccountAccessPoliciesResponse.groupAccessPolicies.map( + (ap) => { + return this.createGroupServiceAccountAccessPolicyView(ap); + } + ); + return view; + } + + private createUserServiceAccountAccessPolicyView( + response: UserServiceAccountAccessPolicyResponse + ): UserServiceAccountAccessPolicyView { + return { + ...this.createBaseAccessPolicyView(response), + grantedServiceAccountId: response.grantedServiceAccountId, + organizationUserId: response.organizationUserId, + organizationUserName: response.organizationUserName, + }; + } + + private createGroupServiceAccountAccessPolicyView( + response: GroupServiceAccountAccessPolicyResponse + ): GroupServiceAccountAccessPolicyView { + return { + ...this.createBaseAccessPolicyView(response), + grantedServiceAccountId: response.grantedServiceAccountId, + groupId: response.groupId, + groupName: response.groupName, + }; + } + + async getPeoplePotentialGrantees(organizationId: string) { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/access-policies/people/potential-grantees", + null, + true, + true + ); + const results = new ListResponse(r, PotentialGranteeResponse); + return await this.createPotentialGranteeViews(organizationId, results.data); + } + + async getServiceAccountsPotentialGrantees(organizationId: string) { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/access-policies/service-accounts/potential-grantees", + null, + true, + true + ); + const results = new ListResponse(r, PotentialGranteeResponse); + return await this.createPotentialGranteeViews(organizationId, results.data); + } + + protected async getOrganizationKey(organizationId: string): Promise { + return await this.cryptoService.getOrgKey(organizationId); + } + + protected getAccessPolicyRequest( granteeId: string, view: | UserProjectAccessPolicyView + | UserServiceAccountAccessPolicyView | GroupProjectAccessPolicyView + | GroupServiceAccountAccessPolicyView | ServiceAccountProjectAccessPolicyView ) { const request = new AccessPolicyRequest(); @@ -166,65 +338,12 @@ export class AccessPolicyService { return request; } - private async createProjectAccessPoliciesView( - organizationId: string, - projectAccessPoliciesResponse: ProjectAccessPoliciesResponse - ): Promise { - const orgKey = await this.getOrganizationKey(organizationId); - const view = new ProjectAccessPoliciesView(); - - view.userAccessPolicies = projectAccessPoliciesResponse.userAccessPolicies.map((ap) => { - return this.createUserProjectAccessPolicyView(ap); - }); - view.groupAccessPolicies = projectAccessPoliciesResponse.groupAccessPolicies.map((ap) => { - return this.createGroupProjectAccessPolicyView(ap); - }); - view.serviceAccountAccessPolicies = await Promise.all( - projectAccessPoliciesResponse.serviceAccountAccessPolicies.map(async (ap) => { - return await this.createServiceAccountProjectAccessPolicyView(orgKey, ap); - }) - ); - return view; - } - - private createUserProjectAccessPolicyView( - response: UserProjectAccessPolicyResponse - ): UserProjectAccessPolicyView { - const view = this.createBaseAccessPolicyView(response); - view.grantedProjectId = response.grantedProjectId; - view.organizationUserId = response.organizationUserId; - view.organizationUserName = response.organizationUserName; - return view; - } - - private createGroupProjectAccessPolicyView( - response: GroupProjectAccessPolicyResponse - ): GroupProjectAccessPolicyView { - const view = this.createBaseAccessPolicyView(response); - view.grantedProjectId = response.grantedProjectId; - view.groupId = response.groupId; - view.groupName = response.groupName; - return view; - } - - private async createServiceAccountProjectAccessPolicyView( - organizationKey: SymmetricCryptoKey, - response: ServiceAccountProjectAccessPolicyResponse - ): Promise { - const view = this.createBaseAccessPolicyView(response); - view.grantedProjectId = response.grantedProjectId; - view.serviceAccountId = response.serviceAccountId; - view.serviceAccountName = await this.encryptService.decryptToUtf8( - new EncString(response.serviceAccountName), - organizationKey - ); - return view; - } - - private createBaseAccessPolicyView( + protected createBaseAccessPolicyView( response: | UserProjectAccessPolicyResponse + | UserServiceAccountAccessPolicyResponse | GroupProjectAccessPolicyResponse + | GroupServiceAccountAccessPolicyResponse | ServiceAccountProjectAccessPolicyResponse ) { return { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html index ff4af9387e0..68fc948659c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html @@ -4,7 +4,7 @@ {{ hint }} @@ -14,8 +14,8 @@ - - + + {{ columnTitle }} @@ -24,22 +24,22 @@ - + - {{ row.name }} - - - - - - - - @@ -51,6 +51,11 @@ + + {{ "canRead" | i18n }} + {{ "canWrite" | i18n }} + {{ "canReadWrite" | i18n }} + @@ -76,7 +81,7 @@ -
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts index 827b7a6a7a3..ba7803160f8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts @@ -1,146 +1,105 @@ -import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { - combineLatestWith, - distinctUntilChanged, - firstValueFrom, - map, - Observable, - Subject, - takeUntil, - tap, -} from "rxjs"; +import { combineLatest, firstValueFrom, Observable, share, Subject, switchMap, tap } from "rxjs"; import { ValidationService } from "@bitwarden/common/abstractions/validation.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; -import { - BaseAccessPolicyView, - GroupProjectAccessPolicyView, - ServiceAccountProjectAccessPolicyView, - UserProjectAccessPolicyView, -} from "../../models/view/access-policy.view"; -import { PotentialGranteeView } from "../../models/view/potential-grantee.view"; -import { ProjectAccessPoliciesView } from "../../models/view/project-access-policies.view"; +import { BaseAccessPolicyView } from "../../models/view/access-policy.view"; import { AccessPolicyService } from "./access-policy.service"; -type RowItemView = { +export type AccessSelectorRowView = { type: "user" | "group" | "serviceAccount"; name: string; - id: string; + granteeId: string; + accessPolicyId: string; read: boolean; write: boolean; icon: string; + static?: boolean; }; @Component({ selector: "sm-access-selector", templateUrl: "./access-selector.component.html", }) -export class AccessSelectorComponent implements OnInit, OnDestroy { +export class AccessSelectorComponent implements OnInit { + static readonly userIcon = "bwi-user"; + static readonly groupIcon = "bwi-family"; + static readonly serviceAccountIcon = "bwi-wrench"; + + @Output() onCreateAccessPolicies = new EventEmitter(); + @Input() label: string; @Input() hint: string; - @Input() tableType: "projectPeople" | "projectServiceAccounts"; @Input() columnTitle: string; @Input() emptyMessage: string; + @Input() granteeType: "people" | "serviceAccounts"; - private readonly userIcon = "bwi-user"; - private readonly groupIcon = "bwi-family"; - private readonly serviceAccountIcon = "bwi-wrench"; - - @Input() projectAccessPolicies$: Observable; - @Input() potentialGrantees$: Observable; - - private projectId: string; - private organizationId: string; - private destroy$: Subject = new Subject(); - - protected loading = true; - protected formGroup = new FormGroup({ - multiSelect: new FormControl([], [Validators.required]), - }); - - protected selectItemsView$: Observable; - protected rows$: Observable; - - constructor( - private route: ActivatedRoute, - private accessPolicyService: AccessPolicyService, - private validationService: ValidationService - ) {} - - async ngOnInit(): Promise { - this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params: any) => { - this.organizationId = params.organizationId; - this.projectId = params.projectId; - }); - - this.selectItemsView$ = this.projectAccessPolicies$.pipe( - distinctUntilChanged( - (prev, curr) => this.getAccessPoliciesCount(curr) === this.getAccessPoliciesCount(prev) - ), - combineLatestWith(this.potentialGrantees$), - map(([projectAccessPolicies, potentialGrantees]) => - this.createSelectView(projectAccessPolicies, potentialGrantees) - ), - tap(() => { - this.loading = false; - this.formGroup.enable(); - this.formGroup.reset(); - }) - ); - - this.rows$ = this.projectAccessPolicies$.pipe( - map((policies) => { - const rowData: RowItemView[] = []; - - if (this.tableType === "projectPeople") { - policies.userAccessPolicies.forEach((policy) => { - rowData.push({ - type: "user", - name: policy.organizationUserName, - id: policy.id, - read: policy.read, - write: policy.write, - icon: this.userIcon, - }); - }); - - policies.groupAccessPolicies.forEach((policy) => { - rowData.push({ - type: "group", - name: policy.groupName, - id: policy.id, - read: policy.read, - write: policy.write, - icon: this.groupIcon, - }); - }); - } - - if (this.tableType === "projectServiceAccounts") { - policies.serviceAccountAccessPolicies.forEach((policy) => { - rowData.push({ - type: "serviceAccount", - name: policy.serviceAccountName, - id: policy.id, - read: policy.read, - write: policy.write, - icon: this.serviceAccountIcon, - }); - }); - } - return rowData; - }) - ); + protected rows$ = new Subject(); + @Input() private set rows(value: AccessSelectorRowView[]) { + this.rows$.next(value); } - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); + private maxLength = 15; + protected formGroup = new FormGroup({ + multiSelect: new FormControl([], [Validators.required, Validators.maxLength(this.maxLength)]), + }); + protected loading = true; + + protected selectItems$: Observable = combineLatest([ + this.rows$, + this.route.params, + ]).pipe( + switchMap(([rows, params]) => + this.getPotentialGrantees(params.organizationId).then((grantees) => + grantees + .filter((g) => !rows.some((row) => row.granteeId === g.id)) + .map((granteeView) => { + let icon: string; + let listName = granteeView.name; + let labelName = granteeView.name; + if (granteeView.type === "user") { + icon = AccessSelectorComponent.userIcon; + if (Utils.isNullOrWhitespace(granteeView.name)) { + listName = granteeView.email; + labelName = granteeView.email; + } else { + listName = `${granteeView.name} (${granteeView.email})`; + } + } else if (granteeView.type === "group") { + icon = AccessSelectorComponent.groupIcon; + } else if (granteeView.type === "serviceAccount") { + icon = AccessSelectorComponent.serviceAccountIcon; + } + return { + icon: icon, + id: granteeView.id, + labelName: labelName, + listName: listName, + }; + }) + ) + ), + tap(() => { + this.loading = false; + this.formGroup.reset(); + this.formGroup.enable(); + }), + share() + ); + + constructor( + private accessPolicyService: AccessPolicyService, + private validationService: ValidationService, + private route: ActivatedRoute + ) {} + + ngOnInit(): void { + this.formGroup.disable(); } submit = async () => { @@ -148,120 +107,15 @@ export class AccessSelectorComponent implements OnInit, OnDestroy { if (this.formGroup.invalid) { return; } - this.loading = true; this.formGroup.disable(); + this.loading = true; - this.accessPolicyService.createProjectAccessPolicies( - this.organizationId, - this.projectId, - this.createProjectAccessPoliciesViewFromSelected() - ); + this.onCreateAccessPolicies.emit(this.formGroup.value.multiSelect); - return firstValueFrom(this.selectItemsView$); + return firstValueFrom(this.selectItems$); }; - private createSelectView = ( - projectAccessPolicies: ProjectAccessPoliciesView, - potentialGrantees: PotentialGranteeView[] - ): SelectItemView[] => { - const selectItemsView = potentialGrantees.map((granteeView) => { - let icon: string; - let listName = granteeView.name; - let labelName = granteeView.name; - if (granteeView.type === "user") { - icon = this.userIcon; - if (Utils.isNullOrWhitespace(granteeView.name)) { - listName = granteeView.email; - labelName = granteeView.email; - } else { - listName = `${granteeView.name} (${granteeView.email})`; - } - } else if (granteeView.type === "group") { - icon = this.groupIcon; - } else if (granteeView.type === "serviceAccount") { - icon = this.serviceAccountIcon; - } - return { - icon: icon, - id: granteeView.id, - labelName: labelName, - listName: listName, - }; - }); - return this.filterExistingAccessPolicies(selectItemsView, projectAccessPolicies); - }; - - private createProjectAccessPoliciesViewFromSelected(): ProjectAccessPoliciesView { - const projectAccessPoliciesView = new ProjectAccessPoliciesView(); - projectAccessPoliciesView.userAccessPolicies = this.formGroup.value.multiSelect - ?.filter((selection) => selection.icon === this.userIcon) - ?.map((filtered) => { - const view = new UserProjectAccessPolicyView(); - view.grantedProjectId = this.projectId; - view.organizationUserId = filtered.id; - view.read = true; - view.write = false; - return view; - }); - - projectAccessPoliciesView.groupAccessPolicies = this.formGroup.value.multiSelect - ?.filter((selection) => selection.icon === this.groupIcon) - ?.map((filtered) => { - const view = new GroupProjectAccessPolicyView(); - view.grantedProjectId = this.projectId; - view.groupId = filtered.id; - view.read = true; - view.write = false; - return view; - }); - - projectAccessPoliciesView.serviceAccountAccessPolicies = this.formGroup.value.multiSelect - ?.filter((selection) => selection.icon === this.serviceAccountIcon) - ?.map((filtered) => { - const view = new ServiceAccountProjectAccessPolicyView(); - view.grantedProjectId = this.projectId; - view.serviceAccountId = filtered.id; - view.read = true; - view.write = false; - return view; - }); - return projectAccessPoliciesView; - } - - private getAccessPoliciesCount(projectAccessPoliciesView: ProjectAccessPoliciesView) { - return ( - projectAccessPoliciesView.groupAccessPolicies.length + - projectAccessPoliciesView.serviceAccountAccessPolicies.length + - projectAccessPoliciesView.userAccessPolicies.length - ); - } - - private filterExistingAccessPolicies( - potentialGrantees: SelectItemView[], - projectAccessPolicies: ProjectAccessPoliciesView - ): SelectItemView[] { - return potentialGrantees - .filter( - (potentialGrantee) => - !projectAccessPolicies.serviceAccountAccessPolicies.some( - (ap) => ap.serviceAccountId === potentialGrantee.id - ) - ) - .filter( - (potentialGrantee) => - !projectAccessPolicies.userAccessPolicies.some( - (ap) => ap.organizationUserId === potentialGrantee.id - ) - ) - .filter( - (potentialGrantee) => - !projectAccessPolicies.groupAccessPolicies.some( - (ap) => ap.groupId === potentialGrantee.id - ) - ); - } - - async updateAccessPolicy(target: any, accessPolicyId: string): Promise { + async update(target: any, accessPolicyId: string): Promise { try { const accessPolicyView = new BaseAccessPolicyView(); accessPolicyView.id = accessPolicyId; @@ -286,6 +140,23 @@ export class AccessSelectorComponent implements OnInit, OnDestroy { this.loading = true; this.formGroup.disable(); await this.accessPolicyService.deleteAccessPolicy(accessPolicyId); - return firstValueFrom(this.selectItemsView$); + return firstValueFrom(this.selectItems$); }; + + private getPotentialGrantees(organizationId: string) { + return this.granteeType === "people" + ? this.accessPolicyService.getPeoplePotentialGrantees(organizationId) + : this.accessPolicyService.getServiceAccountsPotentialGrantees(organizationId); + } + + static getAccessItemType(item: SelectItemView) { + switch (item.icon) { + case AccessSelectorComponent.userIcon: + return "user"; + case AccessSelectorComponent.groupIcon: + return "group"; + case AccessSelectorComponent.serviceAccountIcon: + return "serviceAccount"; + } + } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts index d784dafcdc8..967f1602d08 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts @@ -30,6 +30,19 @@ export class UserProjectAccessPolicyResponse extends BaseAccessPolicyResponse { } } +export class UserServiceAccountAccessPolicyResponse extends BaseAccessPolicyResponse { + organizationUserId: string; + organizationUserName: string; + grantedServiceAccountId: string; + + constructor(response: any) { + super(response); + this.organizationUserId = this.getResponseProperty("OrganizationUserId"); + this.organizationUserName = this.getResponseProperty("OrganizationUserName"); + this.grantedServiceAccountId = this.getResponseProperty("GrantedServiceAccountId"); + } +} + export class GroupProjectAccessPolicyResponse extends BaseAccessPolicyResponse { groupId: string; groupName: string; @@ -43,6 +56,19 @@ export class GroupProjectAccessPolicyResponse extends BaseAccessPolicyResponse { } } +export class GroupServiceAccountAccessPolicyResponse extends BaseAccessPolicyResponse { + groupId: string; + groupName: string; + grantedServiceAccountId: string; + + constructor(response: any) { + super(response); + this.groupId = this.getResponseProperty("GroupId"); + this.groupName = this.getResponseProperty("GroupName"); + this.grantedServiceAccountId = this.getResponseProperty("GrantedServiceAccountId"); + } +} + export class ServiceAccountProjectAccessPolicyResponse extends BaseAccessPolicyResponse { serviceAccountId: string; serviceAccountName: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-access-policies.response.ts index 9429dbeb1b1..f041796e592 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-access-policies.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-access-policies.response.ts @@ -13,8 +13,17 @@ export class ProjectAccessPoliciesResponse extends BaseResponse { constructor(response: any) { super(response); - this.userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); - this.groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); - this.serviceAccountAccessPolicies = this.getResponseProperty("ServiceAccountAccessPolicies"); + const userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); + this.userAccessPolicies = userAccessPolicies.map( + (k: any) => new UserProjectAccessPolicyResponse(k) + ); + const groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); + this.groupAccessPolicies = groupAccessPolicies.map( + (k: any) => new GroupProjectAccessPolicyResponse(k) + ); + const serviceAccountAccessPolicies = this.getResponseProperty("ServiceAccountAccessPolicies"); + this.serviceAccountAccessPolicies = serviceAccountAccessPolicies.map( + (k: any) => new ServiceAccountProjectAccessPolicyResponse(k) + ); } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-accounts-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-accounts-access-policies.response.ts new file mode 100644 index 00000000000..d2fc245bb86 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-accounts-access-policies.response.ts @@ -0,0 +1,23 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +import { + GroupServiceAccountAccessPolicyResponse, + UserServiceAccountAccessPolicyResponse, +} from "./access-policy.response"; + +export class ServiceAccountAccessPoliciesResponse extends BaseResponse { + userAccessPolicies: UserServiceAccountAccessPolicyResponse[]; + groupAccessPolicies: GroupServiceAccountAccessPolicyResponse[]; + + constructor(response: any) { + super(response); + const userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); + this.userAccessPolicies = userAccessPolicies.map( + (k: any) => new UserServiceAccountAccessPolicyResponse(k) + ); + const groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); + this.groupAccessPolicies = groupAccessPolicies.map( + (k: any) => new GroupServiceAccountAccessPolicyResponse(k) + ); + } +}