[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 <contact@willmartian.com>
This commit is contained in:
Thomas Avery 2023-02-27 09:43:06 -06:00 committed by GitHub
parent fabc15fa45
commit 84aa7fffd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 760 additions and 457 deletions

View File

@ -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"
},

View File

@ -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[];
}

View File

@ -1,11 +0,0 @@
import {
GroupProjectAccessPolicyView,
ServiceAccountProjectAccessPolicyView,
UserProjectAccessPolicyView,
} from "./access-policy.view";
export class ProjectAccessPoliciesView {
userAccessPolicies: UserProjectAccessPolicyView[];
groupAccessPolicies: GroupProjectAccessPolicyView[];
serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[];
}

View File

@ -1,23 +0,0 @@
<ng-container *ngIf="projectAccessPolicies$; else spinner">
<div class="tw-w-2/5">
<p class="tw-mt-8">
{{ description }}
</p>
<sm-access-selector
[projectAccessPolicies$]="projectAccessPolicies$"
[potentialGrantees$]="potentialGrantees$"
[label]="label"
[hint]="hint"
[tableType]="accessType"
[columnTitle]="columnTitle"
[emptyMessage]="emptyMessage"
>
</sm-access-selector>
</div>
</ng-container>
<ng-template #spinner>
<div class="tw-items-center tw-justify-center tw-pt-64 tw-text-center">
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
</div>
</ng-template>

View File

@ -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<ProjectAccessPoliciesView>;
protected potentialGrantees$: Observable<PotentialGranteeView[]>;
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()
);
}
}

View File

@ -1,9 +1,15 @@
<sm-project-access
accessType="projectPeople"
[description]="'projectPeopleDescription' | i18n"
<div class="tw-w-2/5">
<p class="tw-mt-8">
{{ "projectPeopleDescription" | i18n }}
</p>
<sm-access-selector
[rows]="rows$ | async"
granteeType="people"
[label]="'people' | i18n"
[hint]="'projectPeopleSelectHint' | i18n"
[columnTitle]="'groupSlashUser' | i18n"
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
>
</sm-project-access>
</sm-access-selector>
</div>

View File

@ -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<void>();
private organizationId: string;
private projectId: string;
protected rows$: Observable<AccessSelectorRowView[]> =
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();
}
}

View File

@ -1,9 +1,15 @@
<sm-project-access
accessType="projectServiceAccounts"
[description]="'projectServiceAccountsDescription' | i18n"
<div class="tw-w-2/5">
<p class="tw-mt-8">
{{ "projectServiceAccountsDescription" | i18n }}
</p>
<sm-access-selector
[rows]="rows$ | async"
granteeType="serviceAccounts"
[label]="'serviceAccounts' | i18n"
[hint]="'projectServiceAccountsSelectHint' | i18n"
[columnTitle]="'serviceAccounts' | i18n"
[emptyMessage]="'projectEmptyServiceAccountAccessPolicies' | i18n"
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
>
</sm-project-access>
</sm-access-selector>
</div>

View File

@ -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<void>();
private organizationId: string;
private projectId: string;
protected rows$: Observable<AccessSelectorRowView[]> =
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();
}
}

View File

@ -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,

View File

@ -0,0 +1,15 @@
<div class="tw-mt-4 tw-w-2/5">
<p class="tw-mt-6">
{{ "serviceAccountPeopleDescription" | i18n }}
</p>
<sm-access-selector
[rows]="rows$ | async"
granteeType="people"
[label]="'people' | i18n"
[hint]="'projectPeopleSelectHint' | i18n"
[columnTitle]="'groupSlashUser' | i18n"
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
>
</sm-access-selector>
</div>

View File

@ -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<void>();
private serviceAccountId: string;
protected rows$: Observable<AccessSelectorRowView[]> =
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();
}
}

View File

@ -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,
},
],
},
];

View File

@ -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: [],
})

View File

@ -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<ProjectAccessPoliciesView>();
projectAccessPolicies$ = this._projectAccessPolicies.asObservable();
private _projectAccessPolicyChanges$ = new Subject<ProjectAccessPoliciesView>();
private _serviceAccountAccessPolicyChanges$ = new Subject<ServiceAccountAccessPoliciesView>();
/**
* 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<ServiceAccountAccessPoliciesView> {
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<void> {
await this.apiService.send("DELETE", "/access-policies/" + accessPolicyId, null, true, false);
this._projectAccessPolicies.next(null);
}
async updateAccessPolicy(baseAccessPolicyView: BaseAccessPolicyView): Promise<void> {
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<SymmetricCryptoKey> {
return await this.cryptoService.getOrgKey(organizationId);
async createServiceAccountAccessPolicies(
serviceAccountId: string,
serviceAccountAccessPoliciesView: ServiceAccountAccessPoliciesView
): Promise<ServiceAccountAccessPoliciesView> {
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<void> {
await this.apiService.send("DELETE", "/access-policies/" + accessPolicyId, null, true, false);
this._projectAccessPolicyChanges$.next(null);
this._serviceAccountAccessPolicyChanges$.next(null);
}
async updateAccessPolicy(baseAccessPolicyView: BaseAccessPolicyView): Promise<void> {
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<ProjectAccessPoliciesView> {
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<ServiceAccountProjectAccessPolicyView> {
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<ServiceAccountAccessPoliciesView> {
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<SymmetricCryptoKey> {
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<ProjectAccessPoliciesView> {
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 = <UserProjectAccessPolicyView>this.createBaseAccessPolicyView(response);
view.grantedProjectId = response.grantedProjectId;
view.organizationUserId = response.organizationUserId;
view.organizationUserName = response.organizationUserName;
return view;
}
private createGroupProjectAccessPolicyView(
response: GroupProjectAccessPolicyResponse
): GroupProjectAccessPolicyView {
const view = <GroupProjectAccessPolicyView>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<ServiceAccountProjectAccessPolicyView> {
const view = <ServiceAccountProjectAccessPolicyView>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 {

View File

@ -4,7 +4,7 @@
<bit-multi-select
class="tw-mr-4 tw-w-full"
formControlName="multiSelect"
[baseItems]="selectItemsView$ | async"
[baseItems]="selectItems$ | async"
[loading]="loading"
></bit-multi-select>
<bit-hint>{{ hint }}</bit-hint>
@ -14,8 +14,8 @@
</bit-form-field>
</form>
<ng-container *ngIf="rows$ | async as rows; else spinner">
<bit-table>
<ng-container>
<bit-table *ngIf="rows$ | async as rows; else spinner">
<ng-container header>
<tr>
<th bitCell colspan="2">{{ columnTitle }}</th>
@ -24,22 +24,22 @@
</ng-container>
<ng-template body>
<ng-container *ngIf="rows?.length > 0; else empty">
<ng-container *ngIf="rows.length > 0; else empty">
<tr bitRow *ngFor="let row of rows">
<td bitCell class="tw-w-0 tw-pr-0">
<i class="bwi {{ row.icon }} tw-text-muted" aria-hidden="true"></i>
</td>
<td bitCell>{{ row.name }}</td>
<td bitCell *ngIf="row.type == 'serviceAccount'">
<bit-form-field class="tw-inline-block tw-w-auto">
<select bitInput disabled>
<option value="canRead" selected>{{ "canRead" | i18n }}</option>
</select>
</bit-form-field>
</td>
<td bitCell *ngIf="row.type != 'serviceAccount'">
<bit-form-field class="tw-inline-block tw-w-auto">
<select bitInput (change)="updateAccessPolicy($event.target, row.id)">
<td bitCell class="tw-max-w-sm tw-truncate">{{ row.name }}</td>
<td bitCell>
<bit-form-field
*ngIf="!row.static; else staticPermissions"
class="tw-mb-auto tw-inline-block tw-w-auto"
>
<select
bitInput
(change)="update($event.target, row.accessPolicyId)"
[disabled]="row.static"
>
<option value="canRead" [selected]="row.read && row.write != true">
{{ "canRead" | i18n }}
</option>
@ -51,6 +51,11 @@
</option>
</select>
</bit-form-field>
<ng-template #staticPermissions>
<span *ngIf="row.read && row.write != true">{{ "canRead" | i18n }}</span>
<span *ngIf="row.read != true && row.write">{{ "canWrite" | i18n }}</span>
<span *ngIf="row.read && row.write">{{ "canReadWrite" | i18n }}</span>
</ng-template>
</td>
<td bitCell class="tw-w-0">
<button
@ -60,7 +65,7 @@
size="default"
[attr.title]="'close' | i18n"
[attr.aria-label]="'close' | i18n"
[bitAction]="delete(row.id)"
[bitAction]="delete(row.accessPolicyId)"
></button>
</td>
</tr>
@ -76,7 +81,7 @@
</ng-template>
<ng-template #spinner>
<div class="tw-items-center tw-justify-center tw-pt-64 tw-text-center">
<div class="tw-items-center tw-justify-center tw-pt-10 tw-text-center">
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
</div>
</ng-template>

View File

@ -1,175 +1,69 @@
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<SelectItemView[]>();
@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";
protected rows$ = new Subject<AccessSelectorRowView[]>();
@Input() private set rows(value: AccessSelectorRowView[]) {
this.rows$.next(value);
}
@Input() projectAccessPolicies$: Observable<ProjectAccessPoliciesView>;
@Input() potentialGrantees$: Observable<PotentialGranteeView[]>;
private projectId: string;
private organizationId: string;
private destroy$: Subject<void> = new Subject<void>();
protected loading = true;
private maxLength = 15;
protected formGroup = new FormGroup({
multiSelect: new FormControl([], [Validators.required]),
multiSelect: new FormControl([], [Validators.required, Validators.maxLength(this.maxLength)]),
});
protected loading = true;
protected selectItemsView$: Observable<SelectItemView[]>;
protected rows$: Observable<RowItemView[]>;
constructor(
private route: ActivatedRoute,
private accessPolicyService: AccessPolicyService,
private validationService: ValidationService
) {}
async ngOnInit(): Promise<void> {
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;
})
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
submit = async () => {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
return;
}
this.loading = true;
this.formGroup.disable();
this.accessPolicyService.createProjectAccessPolicies(
this.organizationId,
this.projectId,
this.createProjectAccessPoliciesViewFromSelected()
);
return firstValueFrom(this.selectItemsView$);
};
private createSelectView = (
projectAccessPolicies: ProjectAccessPoliciesView,
potentialGrantees: PotentialGranteeView[]
): SelectItemView[] => {
const selectItemsView = potentialGrantees.map((granteeView) => {
protected selectItems$: Observable<SelectItemView[]> = 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 = this.userIcon;
icon = AccessSelectorComponent.userIcon;
if (Utils.isNullOrWhitespace(granteeView.name)) {
listName = granteeView.email;
labelName = granteeView.email;
@ -177,9 +71,9 @@ export class AccessSelectorComponent implements OnInit, OnDestroy {
listName = `${granteeView.name} (${granteeView.email})`;
}
} else if (granteeView.type === "group") {
icon = this.groupIcon;
icon = AccessSelectorComponent.groupIcon;
} else if (granteeView.type === "serviceAccount") {
icon = this.serviceAccountIcon;
icon = AccessSelectorComponent.serviceAccountIcon;
}
return {
icon: icon,
@ -187,81 +81,41 @@ export class AccessSelectorComponent implements OnInit, OnDestroy {
labelName: labelName,
listName: listName,
};
});
return this.filterExistingAccessPolicies(selectItemsView, projectAccessPolicies);
})
)
),
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 () => {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
return;
}
this.formGroup.disable();
this.loading = true;
this.onCreateAccessPolicies.emit(this.formGroup.value.multiSelect);
return firstValueFrom(this.selectItems$);
};
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<void> {
async update(target: any, accessPolicyId: string): Promise<void> {
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";
}
}
}

View File

@ -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;

View File

@ -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)
);
}
}

View File

@ -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)
);
}
}