From 1d5115f19050d81d58723b435581188928809bf0 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:51:02 -0400 Subject: [PATCH] Making accessing a project only use one api call to getbyprojectid (#15854) --- .../guards/project-access.guard.spec.ts | 135 ------------------ .../projects/guards/project-access.guard.ts | 33 ----- .../project/project-secrets.component.html | 8 +- .../project/project-secrets.component.ts | 33 +---- .../projects/project/project.component.html | 2 +- .../projects/projects-routing.module.ts | 2 - 6 files changed, 12 insertions(+), 201 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts delete mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts deleted file mode 100644 index 7523fa14a21..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Component } from "@angular/core"; -import { TestBed } from "@angular/core/testing"; -import { Router } from "@angular/router"; -import { RouterTestingModule } from "@angular/router/testing"; -import { MockProxy, mock } from "jest-mock-extended"; -import { of } from "rxjs"; - -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { RouterService } from "@bitwarden/web-vault/app/core"; - -import { ProjectView } from "../../models/view/project.view"; -import { ProjectService } from "../project.service"; - -import { projectAccessGuard } from "./project-access.guard"; - -@Component({ - template: "", - standalone: false, -}) -export class GuardedRouteTestComponent {} - -@Component({ - template: "", - standalone: false, -}) -export class RedirectTestComponent {} - -describe("Project Redirect Guard", () => { - let organizationService: MockProxy; - let routerService: MockProxy; - let projectServiceMock: MockProxy; - let i18nServiceMock: MockProxy; - let toastService: MockProxy; - let router: Router; - let accountService: FakeAccountService; - const userId = Utils.newGuid() as UserId; - - const smOrg1 = { id: "123", canAccessSecretsManager: true } as Organization; - const projectView = { - id: "123", - organizationId: "123", - name: "project-name", - creationDate: Date.now.toString(), - revisionDate: Date.now.toString(), - read: true, - write: true, - } as ProjectView; - - beforeEach(async () => { - organizationService = mock(); - routerService = mock(); - projectServiceMock = mock(); - i18nServiceMock = mock(); - toastService = mock(); - accountService = mockAccountServiceWith(userId); - - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes([ - { - path: "sm/:organizationId/projects/:projectId", - component: GuardedRouteTestComponent, - canActivate: [projectAccessGuard], - }, - { - path: "sm", - component: RedirectTestComponent, - }, - { - path: "sm/:organizationId/projects", - component: RedirectTestComponent, - }, - ]), - ], - providers: [ - { provide: OrganizationService, useValue: organizationService }, - { provide: AccountService, useValue: accountService }, - { provide: RouterService, useValue: routerService }, - { provide: ProjectService, useValue: projectServiceMock }, - { provide: I18nService, useValue: i18nServiceMock }, - { provide: ToastService, useValue: toastService }, - ], - }); - - router = TestBed.inject(Router); - }); - - it("redirects to sm/{orgId}/projects/{projectId} if project exists", async () => { - // Arrange - organizationService.organizations$.mockReturnValue(of([smOrg1])); - projectServiceMock.getByProjectId.mockReturnValue(Promise.resolve(projectView)); - - // Act - await router.navigateByUrl("sm/123/projects/123"); - - // Assert - expect(router.url).toBe("/sm/123/projects/123"); - }); - - it("redirects to sm/projects if project does not exist", async () => { - // Arrange - organizationService.organizations$.mockReturnValue(of([smOrg1])); - - // Act - await router.navigateByUrl("sm/123/projects/124"); - - // Assert - expect(router.url).toBe("/sm/123/projects"); - }); - - it("redirects to sm/123/projects if exception occurs while looking for Project", async () => { - // Arrange - jest.spyOn(projectServiceMock, "getByProjectId").mockImplementation(() => { - throw new Error("Test error"); - }); - jest.spyOn(i18nServiceMock, "t").mockReturnValue("Project not found"); - - // Act - await router.navigateByUrl("sm/123/projects/123"); - // Assert - expect(toastService.showToast).toHaveBeenCalledWith({ - variant: "error", - title: null, - message: "Project not found", - }); - expect(router.url).toBe("/sm/123/projects"); - }); -}); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts deleted file mode 100644 index 2c6723a56a2..00000000000 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts +++ /dev/null @@ -1,33 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { inject } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ToastService } from "@bitwarden/components"; - -import { ProjectService } from "../project.service"; - -/** - * Redirects to projects list if the user doesn't have access to project. - */ -export const projectAccessGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const projectService = inject(ProjectService); - const toastService = inject(ToastService); - const i18nService = inject(I18nService); - - try { - const project = await projectService.getByProjectId(route.params.projectId); - if (project) { - return true; - } - } catch { - toastService.showToast({ - variant: "error", - title: null, - message: i18nService.t("notFound", i18nService.t("project")), - }); - return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "projects"]); - } - return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "projects"]); -}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html index d9919ef6bac..7a2968cfb2c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html @@ -1,7 +1,7 @@ - - + +
; private organizationEnabled: boolean; + protected project = inject(ROUTER_OUTLET_DATA) as Signal; + readonly writeAccess = computed(() => this.project().write); + readonly projectExists = computed(() => !!this.project()); constructor( private route: ActivatedRoute, - private projectService: ProjectService, private secretService: SecretService, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, @@ -68,21 +61,9 @@ export class ProjectSecretsComponent implements OnInit { ) {} ngOnInit() { - // Refresh list if project is edited - const currentProjectEdited = this.projectService.project$.pipe( - filter((p) => p?.id === this.projectId), - startWith(null), - ); - - this.project$ = combineLatest([this.route.params, currentProjectEdited]).pipe( - switchMap(([params, _]) => { - return this.projectService.getByProjectId(params.projectId); - }), - ); - this.secrets$ = this.secretService.secret$.pipe( startWith(null), - combineLatestWith(this.route.params, currentProjectEdited), + combineLatestWith(this.route.params), switchMap(async ([_, params]) => { this.organizationId = params.organizationId; this.projectId = params.projectId; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html index d44d7898cac..ef7c8ff1275 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.html @@ -36,4 +36,4 @@ {{ "editProject" | i18n }} - + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts index 231486703c9..6078520989a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts @@ -1,7 +1,6 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { projectAccessGuard } from "./guards/project-access.guard"; import { ProjectPeopleComponent } from "./project/project-people.component"; import { ProjectSecretsComponent } from "./project/project-secrets.component"; import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component"; @@ -16,7 +15,6 @@ const routes: Routes = [ { path: ":projectId", component: ProjectComponent, - canActivate: [projectAccessGuard], children: [ { path: "",