mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[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:
@@ -6362,6 +6362,9 @@
|
|||||||
"projectEmptyServiceAccountAccessPolicies": {
|
"projectEmptyServiceAccountAccessPolicies": {
|
||||||
"message": "Add service accounts to grant access"
|
"message": "Add service accounts to grant access"
|
||||||
},
|
},
|
||||||
|
"serviceAccountPeopleDescription": {
|
||||||
|
"message": "Grant groups or people access to this service account."
|
||||||
|
},
|
||||||
"canWrite": {
|
"canWrite": {
|
||||||
"message": "Can write"
|
"message": "Can write"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,14 +12,37 @@ export class UserProjectAccessPolicyView extends BaseAccessPolicyView {
|
|||||||
grantedProjectId: string;
|
grantedProjectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UserServiceAccountAccessPolicyView extends BaseAccessPolicyView {
|
||||||
|
organizationUserId: string;
|
||||||
|
organizationUserName: string;
|
||||||
|
grantedServiceAccountId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GroupProjectAccessPolicyView extends BaseAccessPolicyView {
|
export class GroupProjectAccessPolicyView extends BaseAccessPolicyView {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
groupName: string;
|
groupName: string;
|
||||||
grantedProjectId: string;
|
grantedProjectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GroupServiceAccountAccessPolicyView extends BaseAccessPolicyView {
|
||||||
|
groupId: string;
|
||||||
|
groupName: string;
|
||||||
|
grantedServiceAccountId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ServiceAccountProjectAccessPolicyView extends BaseAccessPolicyView {
|
export class ServiceAccountProjectAccessPolicyView extends BaseAccessPolicyView {
|
||||||
serviceAccountId: string;
|
serviceAccountId: string;
|
||||||
serviceAccountName: string;
|
serviceAccountName: string;
|
||||||
grantedProjectId: string;
|
grantedProjectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ProjectAccessPoliciesView {
|
||||||
|
userAccessPolicies: UserProjectAccessPolicyView[];
|
||||||
|
groupAccessPolicies: GroupProjectAccessPolicyView[];
|
||||||
|
serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServiceAccountAccessPoliciesView {
|
||||||
|
userAccessPolicies: UserServiceAccountAccessPolicyView[];
|
||||||
|
groupAccessPolicies: GroupServiceAccountAccessPolicyView[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import {
|
|
||||||
GroupProjectAccessPolicyView,
|
|
||||||
ServiceAccountProjectAccessPolicyView,
|
|
||||||
UserProjectAccessPolicyView,
|
|
||||||
} from "./access-policy.view";
|
|
||||||
|
|
||||||
export class ProjectAccessPoliciesView {
|
|
||||||
userAccessPolicies: UserProjectAccessPolicyView[];
|
|
||||||
groupAccessPolicies: GroupProjectAccessPolicyView[];
|
|
||||||
serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[];
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
<sm-project-access
|
<div class="tw-w-2/5">
|
||||||
accessType="projectPeople"
|
<p class="tw-mt-8">
|
||||||
[description]="'projectPeopleDescription' | i18n"
|
{{ "projectPeopleDescription" | i18n }}
|
||||||
[label]="'people' | i18n"
|
</p>
|
||||||
[hint]="'projectPeopleSelectHint' | i18n"
|
<sm-access-selector
|
||||||
[columnTitle]="'groupSlashUser' | i18n"
|
[rows]="rows$ | async"
|
||||||
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
granteeType="people"
|
||||||
>
|
[label]="'people' | i18n"
|
||||||
</sm-project-access>
|
[hint]="'projectPeopleSelectHint' | i18n"
|
||||||
|
[columnTitle]="'groupSlashUser' | i18n"
|
||||||
|
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
||||||
|
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
||||||
|
>
|
||||||
|
</sm-access-selector>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -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({
|
@Component({
|
||||||
selector: "sm-project-people",
|
selector: "sm-project-people",
|
||||||
templateUrl: "./project-people.component.html",
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<sm-project-access
|
<div class="tw-w-2/5">
|
||||||
accessType="projectServiceAccounts"
|
<p class="tw-mt-8">
|
||||||
[description]="'projectServiceAccountsDescription' | i18n"
|
{{ "projectServiceAccountsDescription" | i18n }}
|
||||||
[label]="'serviceAccounts' | i18n"
|
</p>
|
||||||
[hint]="'projectServiceAccountsSelectHint' | i18n"
|
<sm-access-selector
|
||||||
[columnTitle]="'serviceAccounts' | i18n"
|
[rows]="rows$ | async"
|
||||||
[emptyMessage]="'projectEmptyServiceAccountAccessPolicies' | i18n"
|
granteeType="serviceAccounts"
|
||||||
>
|
[label]="'serviceAccounts' | i18n"
|
||||||
</sm-project-access>
|
[hint]="'projectServiceAccountsSelectHint' | i18n"
|
||||||
|
[columnTitle]="'serviceAccounts' | i18n"
|
||||||
|
[emptyMessage]="'projectEmptyServiceAccountAccessPolicies' | i18n"
|
||||||
|
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
||||||
|
>
|
||||||
|
</sm-access-selector>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -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({
|
@Component({
|
||||||
selector: "sm-project-service-accounts",
|
selector: "sm-project-service-accounts",
|
||||||
templateUrl: "./project-service-accounts.component.html",
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
|||||||
|
|
||||||
import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component";
|
import { ProjectDeleteDialogComponent } from "./dialog/project-delete-dialog.component";
|
||||||
import { ProjectDialogComponent } from "./dialog/project-dialog.component";
|
import { ProjectDialogComponent } from "./dialog/project-dialog.component";
|
||||||
import { ProjectAccessComponent } from "./project/project-access.component";
|
|
||||||
import { ProjectPeopleComponent } from "./project/project-people.component";
|
import { ProjectPeopleComponent } from "./project/project-people.component";
|
||||||
import { ProjectSecretsComponent } from "./project/project-secrets.component";
|
import { ProjectSecretsComponent } from "./project/project-secrets.component";
|
||||||
import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component";
|
import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component";
|
||||||
@@ -18,7 +17,6 @@ import { ProjectsComponent } from "./projects/projects.component";
|
|||||||
imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule],
|
imports: [SecretsManagerSharedModule, ProjectsRoutingModule, BreadcrumbsModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectsComponent,
|
ProjectsComponent,
|
||||||
ProjectAccessComponent,
|
|
||||||
ProjectDialogComponent,
|
ProjectDialogComponent,
|
||||||
ProjectDeleteDialogComponent,
|
ProjectDeleteDialogComponent,
|
||||||
ProjectPeopleComponent,
|
ProjectPeopleComponent,
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
|
|||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AccessTokenComponent } from "./access/access-tokens.component";
|
import { AccessTokenComponent } from "./access/access-tokens.component";
|
||||||
|
import { ServiceAccountPeopleComponent } from "./people/service-account-people.component";
|
||||||
import { ServiceAccountComponent } from "./service-account.component";
|
import { ServiceAccountComponent } from "./service-account.component";
|
||||||
import { ServiceAccountsComponent } from "./service-accounts.component";
|
import { ServiceAccountsComponent } from "./service-accounts.component";
|
||||||
|
|
||||||
@@ -23,6 +24,10 @@ const routes: Routes = [
|
|||||||
path: "access",
|
path: "access",
|
||||||
component: AccessTokenComponent,
|
component: AccessTokenComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "people",
|
||||||
|
component: ServiceAccountPeopleComponent,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-
|
|||||||
import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component";
|
import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component";
|
||||||
import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component";
|
import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component";
|
||||||
import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component";
|
import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component";
|
||||||
|
import { ServiceAccountPeopleComponent } from "./people/service-account-people.component";
|
||||||
import { ServiceAccountComponent } from "./service-account.component";
|
import { ServiceAccountComponent } from "./service-account.component";
|
||||||
import { ServiceAccountsListComponent } from "./service-accounts-list.component";
|
import { ServiceAccountsListComponent } from "./service-accounts-list.component";
|
||||||
import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module";
|
import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module";
|
||||||
@@ -27,6 +28,7 @@ import { ServiceAccountsComponent } from "./service-accounts.component";
|
|||||||
ServiceAccountDialogComponent,
|
ServiceAccountDialogComponent,
|
||||||
ServiceAccountsComponent,
|
ServiceAccountsComponent,
|
||||||
ServiceAccountsListComponent,
|
ServiceAccountsListComponent,
|
||||||
|
ServiceAccountPeopleComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,34 +11,51 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
|||||||
import {
|
import {
|
||||||
BaseAccessPolicyView,
|
BaseAccessPolicyView,
|
||||||
GroupProjectAccessPolicyView,
|
GroupProjectAccessPolicyView,
|
||||||
|
GroupServiceAccountAccessPolicyView,
|
||||||
|
ProjectAccessPoliciesView,
|
||||||
|
ServiceAccountAccessPoliciesView,
|
||||||
ServiceAccountProjectAccessPolicyView,
|
ServiceAccountProjectAccessPolicyView,
|
||||||
UserProjectAccessPolicyView,
|
UserProjectAccessPolicyView,
|
||||||
|
UserServiceAccountAccessPolicyView,
|
||||||
} from "../../models/view/access-policy.view";
|
} from "../../models/view/access-policy.view";
|
||||||
import { PotentialGranteeView } from "../../models/view/potential-grantee.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 { AccessPolicyUpdateRequest } from "./models/requests/access-policy-update.request";
|
||||||
import { AccessPolicyRequest } from "./models/requests/access-policy.request";
|
import { AccessPolicyRequest } from "./models/requests/access-policy.request";
|
||||||
import {
|
import {
|
||||||
|
GroupServiceAccountAccessPolicyResponse,
|
||||||
|
UserServiceAccountAccessPolicyResponse,
|
||||||
GroupProjectAccessPolicyResponse,
|
GroupProjectAccessPolicyResponse,
|
||||||
ServiceAccountProjectAccessPolicyResponse,
|
ServiceAccountProjectAccessPolicyResponse,
|
||||||
UserProjectAccessPolicyResponse,
|
UserProjectAccessPolicyResponse,
|
||||||
} from "./models/responses/access-policy.response";
|
} from "./models/responses/access-policy.response";
|
||||||
import { PotentialGranteeResponse } from "./models/responses/potential-grantee.response";
|
import { PotentialGranteeResponse } from "./models/responses/potential-grantee.response";
|
||||||
import { ProjectAccessPoliciesResponse } from "./models/responses/project-access-policies.response";
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: "root",
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class AccessPolicyService {
|
export class AccessPolicyService {
|
||||||
protected _projectAccessPolicies = new Subject<ProjectAccessPoliciesView>();
|
private _projectAccessPolicyChanges$ = new Subject<ProjectAccessPoliciesView>();
|
||||||
projectAccessPolicies$ = this._projectAccessPolicies.asObservable();
|
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(
|
constructor(
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private apiService: ApiService,
|
protected apiService: ApiService,
|
||||||
private encryptService: EncryptService
|
protected encryptService: EncryptService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getProjectAccessPolicies(
|
async getProjectAccessPolicies(
|
||||||
@@ -57,46 +74,19 @@ export class AccessPolicyService {
|
|||||||
return await this.createProjectAccessPoliciesView(organizationId, results);
|
return await this.createProjectAccessPoliciesView(organizationId, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPeoplePotentialGrantees(organizationId: string) {
|
async getServiceAccountAccessPolicies(
|
||||||
|
serviceAccountId: string
|
||||||
|
): Promise<ServiceAccountAccessPoliciesView> {
|
||||||
const r = await this.apiService.send(
|
const r = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
"/organizations/" + organizationId + "/access-policies/people/potential-grantees",
|
"/service-accounts/" + serviceAccountId + "/access-policies",
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const results = new ListResponse(r, PotentialGranteeResponse);
|
|
||||||
return await this.createPotentialGranteeViews(organizationId, results.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getServiceAccountsPotentialGrantees(organizationId: string) {
|
const results = new ServiceAccountAccessPoliciesResponse(r);
|
||||||
const r = await this.apiService.send(
|
return await this.createServiceAccountAccessPoliciesView(results);
|
||||||
"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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createProjectAccessPolicies(
|
async createProjectAccessPolicies(
|
||||||
@@ -114,12 +104,68 @@ export class AccessPolicyService {
|
|||||||
);
|
);
|
||||||
const results = new ProjectAccessPoliciesResponse(r);
|
const results = new ProjectAccessPoliciesResponse(r);
|
||||||
const view = await this.createProjectAccessPoliciesView(organizationId, results);
|
const view = await this.createProjectAccessPoliciesView(organizationId, results);
|
||||||
this._projectAccessPolicies.next(view);
|
this._projectAccessPolicyChanges$.next(view);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOrganizationKey(organizationId: string): Promise<SymmetricCryptoKey> {
|
async createServiceAccountAccessPolicies(
|
||||||
return await this.cryptoService.getOrgKey(organizationId);
|
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(
|
private getAccessPoliciesCreateRequest(
|
||||||
@@ -152,11 +198,137 @@ export class AccessPolicyService {
|
|||||||
return createRequest;
|
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,
|
granteeId: string,
|
||||||
view:
|
view:
|
||||||
| UserProjectAccessPolicyView
|
| UserProjectAccessPolicyView
|
||||||
|
| UserServiceAccountAccessPolicyView
|
||||||
| GroupProjectAccessPolicyView
|
| GroupProjectAccessPolicyView
|
||||||
|
| GroupServiceAccountAccessPolicyView
|
||||||
| ServiceAccountProjectAccessPolicyView
|
| ServiceAccountProjectAccessPolicyView
|
||||||
) {
|
) {
|
||||||
const request = new AccessPolicyRequest();
|
const request = new AccessPolicyRequest();
|
||||||
@@ -166,65 +338,12 @@ export class AccessPolicyService {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createProjectAccessPoliciesView(
|
protected createBaseAccessPolicyView(
|
||||||
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(
|
|
||||||
response:
|
response:
|
||||||
| UserProjectAccessPolicyResponse
|
| UserProjectAccessPolicyResponse
|
||||||
|
| UserServiceAccountAccessPolicyResponse
|
||||||
| GroupProjectAccessPolicyResponse
|
| GroupProjectAccessPolicyResponse
|
||||||
|
| GroupServiceAccountAccessPolicyResponse
|
||||||
| ServiceAccountProjectAccessPolicyResponse
|
| ServiceAccountProjectAccessPolicyResponse
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<bit-multi-select
|
<bit-multi-select
|
||||||
class="tw-mr-4 tw-w-full"
|
class="tw-mr-4 tw-w-full"
|
||||||
formControlName="multiSelect"
|
formControlName="multiSelect"
|
||||||
[baseItems]="selectItemsView$ | async"
|
[baseItems]="selectItems$ | async"
|
||||||
[loading]="loading"
|
[loading]="loading"
|
||||||
></bit-multi-select>
|
></bit-multi-select>
|
||||||
<bit-hint>{{ hint }}</bit-hint>
|
<bit-hint>{{ hint }}</bit-hint>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ng-container *ngIf="rows$ | async as rows; else spinner">
|
<ng-container>
|
||||||
<bit-table>
|
<bit-table *ngIf="rows$ | async as rows; else spinner">
|
||||||
<ng-container header>
|
<ng-container header>
|
||||||
<tr>
|
<tr>
|
||||||
<th bitCell colspan="2">{{ columnTitle }}</th>
|
<th bitCell colspan="2">{{ columnTitle }}</th>
|
||||||
@@ -24,22 +24,22 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template body>
|
<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">
|
<tr bitRow *ngFor="let row of rows">
|
||||||
<td bitCell class="tw-w-0 tw-pr-0">
|
<td bitCell class="tw-w-0 tw-pr-0">
|
||||||
<i class="bwi {{ row.icon }} tw-text-muted" aria-hidden="true"></i>
|
<i class="bwi {{ row.icon }} tw-text-muted" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
<td bitCell>{{ row.name }}</td>
|
<td bitCell class="tw-max-w-sm tw-truncate">{{ row.name }}</td>
|
||||||
<td bitCell *ngIf="row.type == 'serviceAccount'">
|
<td bitCell>
|
||||||
<bit-form-field class="tw-inline-block tw-w-auto">
|
<bit-form-field
|
||||||
<select bitInput disabled>
|
*ngIf="!row.static; else staticPermissions"
|
||||||
<option value="canRead" selected>{{ "canRead" | i18n }}</option>
|
class="tw-mb-auto tw-inline-block tw-w-auto"
|
||||||
</select>
|
>
|
||||||
</bit-form-field>
|
<select
|
||||||
</td>
|
bitInput
|
||||||
<td bitCell *ngIf="row.type != 'serviceAccount'">
|
(change)="update($event.target, row.accessPolicyId)"
|
||||||
<bit-form-field class="tw-inline-block tw-w-auto">
|
[disabled]="row.static"
|
||||||
<select bitInput (change)="updateAccessPolicy($event.target, row.id)">
|
>
|
||||||
<option value="canRead" [selected]="row.read && row.write != true">
|
<option value="canRead" [selected]="row.read && row.write != true">
|
||||||
{{ "canRead" | i18n }}
|
{{ "canRead" | i18n }}
|
||||||
</option>
|
</option>
|
||||||
@@ -51,6 +51,11 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</bit-form-field>
|
</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>
|
||||||
<td bitCell class="tw-w-0">
|
<td bitCell class="tw-w-0">
|
||||||
<button
|
<button
|
||||||
@@ -60,7 +65,7 @@
|
|||||||
size="default"
|
size="default"
|
||||||
[attr.title]="'close' | i18n"
|
[attr.title]="'close' | i18n"
|
||||||
[attr.aria-label]="'close' | i18n"
|
[attr.aria-label]="'close' | i18n"
|
||||||
[bitAction]="delete(row.id)"
|
[bitAction]="delete(row.accessPolicyId)"
|
||||||
></button>
|
></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -76,7 +81,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #spinner>
|
<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>
|
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -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 { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import {
|
import { combineLatest, firstValueFrom, Observable, share, Subject, switchMap, tap } from "rxjs";
|
||||||
combineLatestWith,
|
|
||||||
distinctUntilChanged,
|
|
||||||
firstValueFrom,
|
|
||||||
map,
|
|
||||||
Observable,
|
|
||||||
Subject,
|
|
||||||
takeUntil,
|
|
||||||
tap,
|
|
||||||
} from "rxjs";
|
|
||||||
|
|
||||||
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view";
|
import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view";
|
||||||
|
|
||||||
import {
|
import { BaseAccessPolicyView } from "../../models/view/access-policy.view";
|
||||||
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 { AccessPolicyService } from "./access-policy.service";
|
import { AccessPolicyService } from "./access-policy.service";
|
||||||
|
|
||||||
type RowItemView = {
|
export type AccessSelectorRowView = {
|
||||||
type: "user" | "group" | "serviceAccount";
|
type: "user" | "group" | "serviceAccount";
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
granteeId: string;
|
||||||
|
accessPolicyId: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
write: boolean;
|
write: boolean;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
static?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "sm-access-selector",
|
selector: "sm-access-selector",
|
||||||
templateUrl: "./access-selector.component.html",
|
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() label: string;
|
||||||
@Input() hint: string;
|
@Input() hint: string;
|
||||||
@Input() tableType: "projectPeople" | "projectServiceAccounts";
|
|
||||||
@Input() columnTitle: string;
|
@Input() columnTitle: string;
|
||||||
@Input() emptyMessage: string;
|
@Input() emptyMessage: string;
|
||||||
|
@Input() granteeType: "people" | "serviceAccounts";
|
||||||
|
|
||||||
private readonly userIcon = "bwi-user";
|
protected rows$ = new Subject<AccessSelectorRowView[]>();
|
||||||
private readonly groupIcon = "bwi-family";
|
@Input() private set rows(value: AccessSelectorRowView[]) {
|
||||||
private readonly serviceAccountIcon = "bwi-wrench";
|
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;
|
|
||||||
protected formGroup = new FormGroup({
|
|
||||||
multiSelect: new FormControl([], [Validators.required]),
|
|
||||||
});
|
|
||||||
|
|
||||||
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 {
|
private maxLength = 15;
|
||||||
this.destroy$.next();
|
protected formGroup = new FormGroup({
|
||||||
this.destroy$.complete();
|
multiSelect: new FormControl([], [Validators.required, Validators.maxLength(this.maxLength)]),
|
||||||
|
});
|
||||||
|
protected loading = true;
|
||||||
|
|
||||||
|
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 = 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 () => {
|
submit = async () => {
|
||||||
@@ -148,120 +107,15 @@ export class AccessSelectorComponent implements OnInit, OnDestroy {
|
|||||||
if (this.formGroup.invalid) {
|
if (this.formGroup.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
|
||||||
this.formGroup.disable();
|
this.formGroup.disable();
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
this.accessPolicyService.createProjectAccessPolicies(
|
this.onCreateAccessPolicies.emit(this.formGroup.value.multiSelect);
|
||||||
this.organizationId,
|
|
||||||
this.projectId,
|
|
||||||
this.createProjectAccessPoliciesViewFromSelected()
|
|
||||||
);
|
|
||||||
|
|
||||||
return firstValueFrom(this.selectItemsView$);
|
return firstValueFrom(this.selectItems$);
|
||||||
};
|
};
|
||||||
|
|
||||||
private createSelectView = (
|
async update(target: any, accessPolicyId: string): Promise<void> {
|
||||||
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<void> {
|
|
||||||
try {
|
try {
|
||||||
const accessPolicyView = new BaseAccessPolicyView();
|
const accessPolicyView = new BaseAccessPolicyView();
|
||||||
accessPolicyView.id = accessPolicyId;
|
accessPolicyView.id = accessPolicyId;
|
||||||
@@ -286,6 +140,23 @@ export class AccessSelectorComponent implements OnInit, OnDestroy {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.formGroup.disable();
|
this.formGroup.disable();
|
||||||
await this.accessPolicyService.deleteAccessPolicy(accessPolicyId);
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
export class GroupProjectAccessPolicyResponse extends BaseAccessPolicyResponse {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
groupName: 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 {
|
export class ServiceAccountProjectAccessPolicyResponse extends BaseAccessPolicyResponse {
|
||||||
serviceAccountId: string;
|
serviceAccountId: string;
|
||||||
serviceAccountName: string;
|
serviceAccountName: string;
|
||||||
|
|||||||
@@ -13,8 +13,17 @@ export class ProjectAccessPoliciesResponse extends BaseResponse {
|
|||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.userAccessPolicies = this.getResponseProperty("UserAccessPolicies");
|
const userAccessPolicies = this.getResponseProperty("UserAccessPolicies");
|
||||||
this.groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies");
|
this.userAccessPolicies = userAccessPolicies.map(
|
||||||
this.serviceAccountAccessPolicies = this.getResponseProperty("ServiceAccountAccessPolicies");
|
(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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user