diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 52291a20a7..06d42c4265 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9002,6 +9002,24 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "environmentVariables": { + "message": "Environment variables" + }, + "organizationId": { + "message": "Organization ID" + }, + "projectIds": { + "message": "Project IDs" + }, + "projectId": { + "message": "Project ID" + }, + "projectsAccessedByMachineAccount": { + "message": "The following projects can be accessed by this machine account." + }, + "config": { + "message": "Config" + }, "learnMoreAboutEmergencyAccess": { "message":"Learn more about emergency access" }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts index 03a0353352..5f0aa9647c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts @@ -6,4 +6,5 @@ export class ProjectListView { revisionDate: string; read: boolean; write: boolean; + linkable: boolean; } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts index 3b4add0c36..282980ece7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts @@ -131,6 +131,7 @@ export class ProjectService { ); projectListView.creationDate = s.creationDate; projectListView.revisionDate = s.revisionDate; + projectListView.linkable = true; return projectListView; }), ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html new file mode 100644 index 0000000000..b17e47a39e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html @@ -0,0 +1,47 @@ +
+
+

{{ "environmentVariables" | i18n }}

+
+ + {{ "identityUrl" | i18n }} + + + + + {{ "apiUrl" | i18n }} + + + +
+ + {{ "organizationId" | i18n }} + + + +
+
+

{{ "projectIds" | i18n }}

+

{{ "projectsNoItemsTitle" | i18n }}

+

{{ "projectsAccessedByMachineAccount" | i18n }}

+ +
+
+
+ +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts new file mode 100644 index 0000000000..47deeb2a41 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -0,0 +1,127 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Params } from "@angular/router"; +import { Subject, concatMap, takeUntil } from "rxjs"; + +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; + +import { ProjectListView } from "../../models/view/project-list.view"; +import { ProjectService } from "../../projects/project.service"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; + +class ServiceAccountConfig { + organizationId: string; + serviceAccountId: string; + identityUrl: string; + apiUrl: string; + projects: ProjectListView[]; +} + +@Component({ + selector: "sm-service-account-config", + templateUrl: "./config.component.html", +}) +export class ServiceAccountConfigComponent implements OnInit, OnDestroy { + identityUrl: string; + apiUrl: string; + organizationId: string; + serviceAccountId: string; + projects: ProjectListView[]; + hasProjects = false; + + private destroy$ = new Subject(); + loading = true; + + constructor( + private environmentService: EnvironmentService, + private route: ActivatedRoute, + private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, + private i18nService: I18nService, + private projectService: ProjectService, + private accessPolicyService: AccessPolicyService, + ) {} + + async ngOnInit() { + this.route.params + .pipe( + concatMap(async (params: Params) => { + return await this.load(params.organizationId, params.serviceAccountId); + }), + takeUntil(this.destroy$), + ) + .subscribe((smConfig) => { + this.identityUrl = smConfig.identityUrl; + this.apiUrl = smConfig.apiUrl; + this.organizationId = smConfig.organizationId; + this.serviceAccountId = smConfig.serviceAccountId; + this.projects = smConfig.projects; + + this.hasProjects = smConfig.projects.length > 0; + this.loading = false; + }); + } + + async load(organizationId: string, serviceAccountId: string): Promise { + const environment = await this.environmentService.getEnvironment(); + + const allProjects = await this.projectService.getProjects(organizationId); + const policies = await this.accessPolicyService.getServiceAccountGrantedPolicies( + organizationId, + serviceAccountId, + ); + + const projects = policies.grantedProjectPolicies.map((policy) => { + return { + id: policy.accessPolicy.grantedProjectId, + name: policy.accessPolicy.grantedProjectName, + organizationId: organizationId, + linkable: allProjects.some( + (project) => project.id === policy.accessPolicy.grantedProjectId, + ), + } as ProjectListView; + }); + + return { + organizationId: organizationId, + serviceAccountId: serviceAccountId, + identityUrl: environment.getIdentityUrl(), + apiUrl: environment.getApiUrl(), + projects: projects, + } as ServiceAccountConfig; + } + + copyIdentityUrl = () => { + this.platformUtilsService.copyToClipboard(this.identityUrl); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("identityUrl")), + }); + }; + + copyApiUrl = () => { + this.platformUtilsService.copyToClipboard(this.apiUrl); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("apiUrl")), + }); + }; + + copyOrganizationId = () => { + this.platformUtilsService.copyToClipboard(this.organizationId); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("organizationId")), + }); + }; + + 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 c9ce8d8c64..392bfcb806 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 @@ -29,6 +29,7 @@ {{ "eventLogs" | i18n }} + {{ "config" | i18n }} - +
- {{ - project.name - }} +
+ {{ project.name }} + {{ project.name }} +
+ {{ project.id }} + +
+
- {{ project.revisionDate | date: "medium" }} + + {{ project.revisionDate | date: "medium" }} + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 8c6a5e97dc..2d01908a36 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -24,6 +24,8 @@ export class ProjectsListComponent { } private _projects: ProjectListView[]; + @Input() showMenus?: boolean = true; + @Input() set search(search: string) { this.selection.clear(); @@ -33,6 +35,7 @@ export class ProjectsListComponent { @Output() editProjectEvent = new EventEmitter(); @Output() deleteProjectEvent = new EventEmitter(); @Output() newProjectEvent = new EventEmitter(); + @Output() copiedProjectUUIdEvent = new EventEmitter(); selection = new SelectionModel(true, []); protected dataSource = new TableDataSource(); @@ -90,4 +93,13 @@ export class ProjectsListComponent { } return false; } + + copyProjectUuidToClipboard(id: string) { + this.platformUtilsService.copyToClipboard(id); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("valueCopied", this.i18nService.t("projectId")), + ); + } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts index cb723af6d7..d778db9814 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts @@ -1,10 +1,12 @@ import { NgModule } from "@angular/core"; import { + CardComponent, MultiSelectModule, SearchModule, SelectModule, NoItemsModule, + FormFieldModule, } from "@bitwarden/components"; import { CoreOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/core"; import { DynamicAvatarComponent } from "@bitwarden/web-vault/app/components/dynamic-avatar.component"; @@ -31,17 +33,21 @@ import { SecretsListComponent } from "./secrets-list.component"; DynamicAvatarComponent, SearchModule, HeaderModule, + CardComponent, + FormFieldModule, ], exports: [ AccessPolicySelectorComponent, BulkConfirmationDialogComponent, BulkStatusDialogComponent, + FormFieldModule, HeaderModule, NewMenuComponent, NoItemsModule, ProjectsListComponent, SearchModule, SecretsListComponent, + CardComponent, SelectModule, SharedModule, ],