diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b06e0144e16..dbbb02e93e2 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -5864,7 +5864,7 @@
"message": "Service account name"
},
"newSaSelectAccess":{
- "message": "Type or select projects or secrets"
+ "message": "Type or select projects"
},
"newSaTypeToFilter":{
"message": "Type to filter"
@@ -6365,6 +6365,12 @@
"serviceAccountPeopleDescription": {
"message": "Grant groups or people access to this service account."
},
+ "serviceAccountProjectsDescription": {
+ "message": "Assign projects to this service account. "
+ },
+ "serviceAccountEmptyProjectAccesspolicies": {
+ "message": "Add projects to grant access"
+ },
"canWrite": {
"message": "Can write"
},
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts
index e4fc1fb9426..0f93b549f3a 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policy.view.ts
@@ -34,6 +34,7 @@ export class ServiceAccountProjectAccessPolicyView extends BaseAccessPolicyView
serviceAccountId: string;
serviceAccountName: string;
grantedProjectId: string;
+ grantedProjectName: string;
}
export class ProjectAccessPoliciesView {
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts
index d1ee0e3ce10..4a1ea9c7fdb 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts
@@ -36,7 +36,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
rows.push({
type: "user",
name: policy.organizationUserName,
- granteeId: policy.organizationUserId,
+ id: policy.organizationUserId,
accessPolicyId: policy.id,
read: policy.read,
write: policy.write,
@@ -48,7 +48,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
rows.push({
type: "group",
name: policy.groupName,
- granteeId: policy.groupId,
+ id: policy.groupId,
accessPolicyId: policy.id,
read: policy.read,
write: policy.write,
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts
index e442ef4b168..e46c3038b9d 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts
@@ -33,7 +33,7 @@ export class ProjectServiceAccountsComponent implements OnInit, OnDestroy {
policies.serviceAccountAccessPolicies.map((policy) => ({
type: "serviceAccount",
name: policy.serviceAccountName,
- granteeId: policy.serviceAccountId,
+ id: policy.serviceAccountId,
accessPolicyId: policy.id,
read: policy.read,
write: policy.write,
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts
index dbc957ebc5a..302bbd1f359 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts
@@ -36,7 +36,7 @@ export class ServiceAccountPeopleComponent {
rows.push({
type: "user",
name: policy.organizationUserName,
- granteeId: policy.organizationUserId,
+ id: policy.organizationUserId,
accessPolicyId: policy.id,
read: policy.read,
write: policy.write,
@@ -49,7 +49,7 @@ export class ServiceAccountPeopleComponent {
rows.push({
type: "group",
name: policy.groupName,
- granteeId: policy.groupId,
+ id: policy.groupId,
accessPolicyId: policy.id,
read: policy.read,
write: policy.write,
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html
new file mode 100644
index 00000000000..6ceb2dc5f87
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html
@@ -0,0 +1,15 @@
+
+
+ {{ "serviceAccountProjectsDescription" | i18n }}
+
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts
new file mode 100644
index 00000000000..c886523834a
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts
@@ -0,0 +1,78 @@
+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 { 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-service-account-projects",
+ templateUrl: "./service-account-projects.component.html",
+})
+export class ServiceAccountProjectsComponent {
+ private destroy$ = new Subject();
+ private serviceAccountId: string;
+ private organizationId: string;
+
+ protected rows$: Observable =
+ this.accessPolicyService.serviceAccountGrantedPolicyChanges$.pipe(
+ startWith(null),
+ combineLatestWith(this.route.params),
+ switchMap(([_, params]) =>
+ this.accessPolicyService.getGrantedPolicies(params.serviceAccountId, params.organizationId)
+ ),
+ map((policies) => {
+ return policies.map((policy) => {
+ return {
+ type: "project",
+ name: policy.grantedProjectName,
+ id: policy.grantedProjectId,
+ accessPolicyId: policy.id,
+ read: policy.read,
+ write: policy.write,
+ icon: AccessSelectorComponent.projectIcon,
+ static: true,
+ } as AccessSelectorRowView;
+ });
+ })
+ );
+
+ protected handleCreateAccessPolicies(selected: SelectItemView[]) {
+ const serviceAccountProjectAccessPolicyView = selected
+ .filter((selection) => AccessSelectorComponent.getAccessItemType(selection) === "project")
+ .map((filtered) => {
+ const view = new ServiceAccountProjectAccessPolicyView();
+ view.serviceAccountId = this.serviceAccountId;
+ view.grantedProjectId = filtered.id;
+ view.read = true;
+ view.write = false;
+ return view;
+ });
+
+ return this.accessPolicyService.createGrantedPolicies(
+ this.organizationId,
+ this.serviceAccountId,
+ serviceAccountProjectAccessPolicyView
+ );
+ }
+
+ constructor(private route: ActivatedRoute, private accessPolicyService: AccessPolicyService) {}
+
+ ngOnInit(): void {
+ this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
+ this.organizationId = params.organizationId;
+ this.serviceAccountId = params.serviceAccountId;
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
index 12a26bb6cc0..fcc01ca92ac 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html
@@ -10,7 +10,7 @@
- {{ "secrets" | i18n }}
+ {{ "projects" | i18n }}
{{ "people" | i18n }}
{{ "accessTokens" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts
index 1fdca60605d..11aa0fa8f30 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-routing.module.ts
@@ -3,6 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
import { AccessTokenComponent } from "./access/access-tokens.component";
import { ServiceAccountPeopleComponent } from "./people/service-account-people.component";
+import { ServiceAccountProjectsComponent } from "./projects/service-account-projects.component";
import { ServiceAccountComponent } from "./service-account.component";
import { ServiceAccountsComponent } from "./service-accounts.component";
@@ -18,7 +19,7 @@ const routes: Routes = [
{
path: "",
pathMatch: "full",
- redirectTo: "access",
+ redirectTo: "projects",
},
{
path: "access",
@@ -28,6 +29,10 @@ const routes: Routes = [
path: "people",
component: ServiceAccountPeopleComponent,
},
+ {
+ path: "projects",
+ component: ServiceAccountProjectsComponent,
+ },
],
},
];
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts
index e75bf2dc3fc..0019cbc0319 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.module.ts
@@ -11,6 +11,7 @@ import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog
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 { ServiceAccountProjectsComponent } from "./projects/service-account-projects.component";
import { ServiceAccountComponent } from "./service-account.component";
import { ServiceAccountsListComponent } from "./service-accounts-list.component";
import { ServiceAccountsRoutingModule } from "./service-accounts-routing.module";
@@ -20,12 +21,14 @@ import { ServiceAccountsComponent } from "./service-accounts.component";
imports: [SecretsManagerSharedModule, ServiceAccountsRoutingModule, BreadcrumbsModule],
declarations: [
AccessListComponent,
- ExpirationOptionsComponent,
AccessTokenComponent,
AccessTokenCreateDialogComponent,
AccessTokenDialogComponent,
+ ExpirationOptionsComponent,
ServiceAccountComponent,
ServiceAccountDialogComponent,
+ ServiceAccountPeopleComponent,
+ ServiceAccountProjectsComponent,
ServiceAccountsComponent,
ServiceAccountsListComponent,
ServiceAccountPeopleComponent,
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts
index c63abc1b143..79aee4a0e8c 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy.service.ts
@@ -25,6 +25,7 @@ import { ServiceAccountAccessPoliciesResponse } from "../../shared/access-polici
import { AccessPolicyUpdateRequest } from "./models/requests/access-policy-update.request";
import { AccessPolicyRequest } from "./models/requests/access-policy.request";
+import { GrantedPolicyRequest } from "./models/requests/granted-policy.request";
import {
GroupServiceAccountAccessPolicyResponse,
UserServiceAccountAccessPolicyResponse,
@@ -40,6 +41,9 @@ import { PotentialGranteeResponse } from "./models/responses/potential-grantee.r
export class AccessPolicyService {
private _projectAccessPolicyChanges$ = new Subject();
private _serviceAccountAccessPolicyChanges$ = new Subject();
+ private _serviceAccountGrantedPolicyChanges$ = new Subject<
+ ServiceAccountProjectAccessPolicyView[]
+ >();
/**
* Emits when a project access policy is created or deleted.
@@ -52,12 +56,56 @@ export class AccessPolicyService {
readonly serviceAccountAccessPolicyChanges$ =
this._serviceAccountAccessPolicyChanges$.asObservable();
+ /**
+ * Emits when a service account granted policy is created or deleted.
+ */
+ readonly serviceAccountGrantedPolicyChanges$ =
+ this._serviceAccountGrantedPolicyChanges$.asObservable();
+
constructor(
private cryptoService: CryptoService,
protected apiService: ApiService,
protected encryptService: EncryptService
) {}
+ async getGrantedPolicies(
+ serviceAccountId: string,
+ organizationId: string
+ ): Promise {
+ const r = await this.apiService.send(
+ "GET",
+ "/service-accounts/" + serviceAccountId + "/granted-policies",
+ null,
+ true,
+ true
+ );
+
+ const results = new ListResponse(r, ServiceAccountProjectAccessPolicyResponse);
+ return await this.createServiceAccountProjectAccessPolicyViews(results.data, organizationId);
+ }
+
+ async createGrantedPolicies(
+ organizationId: string,
+ serviceAccountId: string,
+ policies: ServiceAccountProjectAccessPolicyView[]
+ ): Promise {
+ const request = this.getGrantedPoliciesCreateRequest(policies);
+ const r = await this.apiService.send(
+ "POST",
+ "/service-accounts/" + serviceAccountId + "/granted-policies",
+ request,
+ true,
+ true
+ );
+ const results = new ListResponse(r, ServiceAccountProjectAccessPolicyResponse);
+ const views = await this.createServiceAccountProjectAccessPolicyViews(
+ results.data,
+ organizationId
+ );
+ this._serviceAccountGrantedPolicyChanges$.next(views);
+ return views;
+ }
+
async getProjectAccessPolicies(
organizationId: string,
projectId: string
@@ -132,6 +180,7 @@ export class AccessPolicyService {
await this.apiService.send("DELETE", "/access-policies/" + accessPolicyId, null, true, false);
this._projectAccessPolicyChanges$.next(null);
this._serviceAccountAccessPolicyChanges$.next(null);
+ this._serviceAccountGrantedPolicyChanges$.next(null);
}
async updateAccessPolicy(baseAccessPolicyView: BaseAccessPolicyView): Promise {
@@ -228,6 +277,10 @@ export class AccessPolicyService {
...this.createBaseAccessPolicyView(response),
grantedProjectId: response.grantedProjectId,
serviceAccountId: response.serviceAccountId,
+ grantedProjectName: await this.encryptService.decryptToUtf8(
+ new EncString(response.grantedProjectName),
+ organizationKey
+ ),
serviceAccountName: await this.encryptService.decryptToUtf8(
new EncString(response.serviceAccountName),
organizationKey
@@ -318,6 +371,18 @@ export class AccessPolicyService {
return await this.createPotentialGranteeViews(organizationId, results.data);
}
+ async getProjectsPotentialGrantees(organizationId: string) {
+ const r = await this.apiService.send(
+ "GET",
+ "/organizations/" + organizationId + "/access-policies/projects/potential-grantees",
+ null,
+ true,
+ true
+ );
+ const results = new ListResponse(r, PotentialGranteeResponse);
+ return await this.createPotentialGranteeViews(organizationId, results.data);
+ }
+
protected async getOrganizationKey(organizationId: string): Promise {
return await this.cryptoService.getOrgKey(organizationId);
}
@@ -367,7 +432,7 @@ export class AccessPolicyService {
view.type = r.type;
view.email = r.email;
- if (r.type === "serviceAccount") {
+ if (r.type === "serviceAccount" || r.type === "project") {
view.name = await this.encryptService.decryptToUtf8(new EncString(r.name), orgKey);
} else {
view.name = r.name;
@@ -376,4 +441,44 @@ export class AccessPolicyService {
})
);
}
+
+ private getGrantedPoliciesCreateRequest(
+ policies: ServiceAccountProjectAccessPolicyView[]
+ ): GrantedPolicyRequest[] {
+ return policies.map((ap) => {
+ const request = new GrantedPolicyRequest();
+ request.grantedId = ap.grantedProjectId;
+ request.read = ap.read;
+ request.write = ap.write;
+ return request;
+ });
+ }
+
+ private async createServiceAccountProjectAccessPolicyViews(
+ responses: ServiceAccountProjectAccessPolicyResponse[],
+ organizationId: string
+ ): Promise {
+ const orgKey = await this.getOrganizationKey(organizationId);
+ return await Promise.all(
+ responses.map(async (response: ServiceAccountProjectAccessPolicyResponse) => {
+ const view = new ServiceAccountProjectAccessPolicyView();
+ view.id = response.id;
+ view.read = response.read;
+ view.write = response.write;
+ view.creationDate = response.creationDate;
+ view.revisionDate = response.revisionDate;
+ view.serviceAccountId = response.serviceAccountId;
+ view.grantedProjectId = response.grantedProjectId;
+ view.serviceAccountName = await this.encryptService.decryptToUtf8(
+ new EncString(response.serviceAccountName),
+ orgKey
+ );
+ view.grantedProjectName = await this.encryptService.decryptToUtf8(
+ new EncString(response.grantedProjectName),
+ orgKey
+ );
+ return view;
+ })
+ );
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html
index 68fc948659c..dd096f382a8 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.html
@@ -63,8 +63,8 @@
bitIconButton="bwi-close"
buttonType="main"
size="default"
- [attr.title]="'close' | i18n"
- [attr.aria-label]="'close' | i18n"
+ [attr.title]="'remove' | i18n"
+ [attr.aria-label]="'remove' | i18n"
[bitAction]="delete(row.accessPolicyId)"
>
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts
index ba7803160f8..7e79c4604d3 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-selector.component.ts
@@ -12,9 +12,9 @@ import { BaseAccessPolicyView } from "../../models/view/access-policy.view";
import { AccessPolicyService } from "./access-policy.service";
export type AccessSelectorRowView = {
- type: "user" | "group" | "serviceAccount";
+ type: "user" | "group" | "serviceAccount" | "project";
name: string;
- granteeId: string;
+ id: string;
accessPolicyId: string;
read: boolean;
write: boolean;
@@ -30,6 +30,7 @@ export class AccessSelectorComponent implements OnInit {
static readonly userIcon = "bwi-user";
static readonly groupIcon = "bwi-family";
static readonly serviceAccountIcon = "bwi-wrench";
+ static readonly projectIcon = "bwi-collection";
@Output() onCreateAccessPolicies = new EventEmitter();
@@ -37,7 +38,7 @@ export class AccessSelectorComponent implements OnInit {
@Input() hint: string;
@Input() columnTitle: string;
@Input() emptyMessage: string;
- @Input() granteeType: "people" | "serviceAccounts";
+ @Input() granteeType: "people" | "serviceAccounts" | "projects";
protected rows$ = new Subject();
@Input() private set rows(value: AccessSelectorRowView[]) {
@@ -57,7 +58,7 @@ export class AccessSelectorComponent implements OnInit {
switchMap(([rows, params]) =>
this.getPotentialGrantees(params.organizationId).then((grantees) =>
grantees
- .filter((g) => !rows.some((row) => row.granteeId === g.id))
+ .filter((g) => !rows.some((row) => row.id === g.id))
.map((granteeView) => {
let icon: string;
let listName = granteeView.name;
@@ -74,6 +75,8 @@ export class AccessSelectorComponent implements OnInit {
icon = AccessSelectorComponent.groupIcon;
} else if (granteeView.type === "serviceAccount") {
icon = AccessSelectorComponent.serviceAccountIcon;
+ } else if (granteeView.type === "project") {
+ icon = AccessSelectorComponent.projectIcon;
}
return {
icon: icon,
@@ -144,9 +147,14 @@ export class AccessSelectorComponent implements OnInit {
};
private getPotentialGrantees(organizationId: string) {
- return this.granteeType === "people"
- ? this.accessPolicyService.getPeoplePotentialGrantees(organizationId)
- : this.accessPolicyService.getServiceAccountsPotentialGrantees(organizationId);
+ switch (this.granteeType) {
+ case "people":
+ return this.accessPolicyService.getPeoplePotentialGrantees(organizationId);
+ case "serviceAccounts":
+ return this.accessPolicyService.getServiceAccountsPotentialGrantees(organizationId);
+ case "projects":
+ return this.accessPolicyService.getProjectsPotentialGrantees(organizationId);
+ }
}
static getAccessItemType(item: SelectItemView) {
@@ -157,6 +165,8 @@ export class AccessSelectorComponent implements OnInit {
return "group";
case AccessSelectorComponent.serviceAccountIcon:
return "serviceAccount";
+ case AccessSelectorComponent.projectIcon:
+ return "project";
}
}
}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts
new file mode 100644
index 00000000000..ddfca2bfb2b
--- /dev/null
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/requests/granted-policy.request.ts
@@ -0,0 +1,5 @@
+export class GrantedPolicyRequest {
+ grantedId: string;
+ read: boolean;
+ write: boolean;
+}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts
index 967f1602d08..58f574f4ad2 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts
@@ -73,11 +73,13 @@ export class ServiceAccountProjectAccessPolicyResponse extends BaseAccessPolicyR
serviceAccountId: string;
serviceAccountName: string;
grantedProjectId: string;
+ grantedProjectName: string;
constructor(response: any) {
super(response);
this.serviceAccountId = this.getResponseProperty("ServiceAccountId");
this.serviceAccountName = this.getResponseProperty("ServiceAccountName");
this.grantedProjectId = this.getResponseProperty("GrantedProjectId");
+ this.grantedProjectName = this.getResponseProperty("GrantedProjectName");
}
}