From b4d1681bb7f681ad01ec5f607ea9d0328fd2f865 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Tue, 7 Jan 2025 17:44:00 -0800 Subject: [PATCH 01/29] initial commit --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 59 ++++++++++++++++++- .../apps/api/src/auth/user-types.enum.ts | 1 + ...plicationOfferingChangeRequest.e2e-spec.ts | 29 +++++++++ ...-change-request.institutions.controller.ts | 6 +- ...institutions.confirmEnrollment.e2e-spec.ts | 33 +++++++++++ ...s.denyConfirmationOfEnrollment.e2e-spec.ts | 29 +++++++++ ...n-of-enrollment.institutions.controller.ts | 6 +- ...ions.controller.createOffering.e2e-spec.ts | 28 +++++++++ ...ntroller.updateProgramOffering.e2e-spec.ts | 30 ++++++++++ ...rogram-offering.institutions.controller.ts | 16 +++-- ...troller.createEducationProgram.e2e-spec.ts | 19 ++++++ ...s.controller.deactivateProgram.e2e-spec.ts | 19 ++++++ ...ucation-program.institutions.controller.ts | 40 ++++++++++++- ...am-info-request.institutions.controller.ts | 5 +- ...troller.saveScholasticStanding.e2e-spec.ts | 29 +++++++++ ...astic-standings.institutions.controller.ts | 3 +- .../auth/institution-auth-helpers.ts | 16 ++++- .../auth/institution-token-helpers.ts | 22 +++++++ .../auth-test/auth-test.controller.ts | 20 +++++++ ...itutions-and-authentication-users.model.ts | 27 ++++++++- ...e-institutions-and-authentication-users.ts | 7 ++- .../sims-db/src/entities/user-types.enum.ts | 1 + .../src/constants/institution.constants.ts | 13 ++++ 23 files changed, 438 insertions(+), 20 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 718b64d76d..1b15dd49ea 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,6 +9,7 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, + createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -21,12 +22,16 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { + authorizeUserTokenForLocation, BEARER_AUTH_TYPE, + getAuthRelatedEntities, + getInstitutionToken, + InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { Student, User } from "@sims/sims-db"; +import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -296,6 +301,58 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }, ); + + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }); + + it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); }); afterAll(async () => { diff --git a/sources/packages/backend/apps/api/src/auth/user-types.enum.ts b/sources/packages/backend/apps/api/src/auth/user-types.enum.ts index 6fd7a5a6b5..ae210a4bda 100644 --- a/sources/packages/backend/apps/api/src/auth/user-types.enum.ts +++ b/sources/packages/backend/apps/api/src/auth/user-types.enum.ts @@ -1,6 +1,7 @@ export enum InstitutionUserTypes { admin = "admin", user = "user", + readOnlyUser = "read-only-user", } export enum InstitutionUserRoles { diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts index 842139a4ee..a111a2a345 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts @@ -20,6 +20,7 @@ import { ApplicationOfferingChangeRequestStatus, ApplicationStatus, InstitutionLocation, + InstitutionUserTypes, OfferingIntensity, } from "@sims/sims-db"; import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validation"; @@ -132,6 +133,34 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl ); }); + it("Should not be able to submit application offering request with a read-only user.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/institutions/location/${collegeELocation.id}/application-offering-change-request`; + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .post(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it("Should throw study overlap error when trying to submit application offering request with an offering, which overlap with students another application.", async () => { // Arrange const savedUser = await db.user.save(createFakeUser()); diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts index a42d0afbf9..a9992cf4a3 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts @@ -43,7 +43,10 @@ import { ApplicationOfferingChangeRequestService, ApplicationService, } from "../../services"; -import { ApplicationOfferingChangeRequestStatus } from "@sims/sims-db"; +import { + ApplicationOfferingChangeRequestStatus, + InstitutionUserTypes, +} from "@sims/sims-db"; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; import { IInstitutionUserToken } from "../../auth"; import { CustomNamedError } from "@sims/utilities"; @@ -271,6 +274,7 @@ export class ApplicationOfferingChangeRequestInstitutionsController extends Base @ApiUnauthorizedResponse({ description: "The location does not have access to the offering.", }) + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Post() async createApplicationOfferingChangeRequest( @UserToken() userToken: IInstitutionUserToken, diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts index 169cd285eb..dd434823ab 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts @@ -10,8 +10,10 @@ import { InstitutionTokenTypes, } from "../../../../testHelpers"; import { + createE2EDataSources, createFakeDisbursementValue, createFakeInstitutionLocation, + E2EDataSources, saveFakeApplicationDisbursements, } from "@sims/test-utils"; import { @@ -23,6 +25,7 @@ import { EducationProgramOffering, Institution, InstitutionLocation, + InstitutionUserTypes, } from "@sims/sims-db"; import { MONEY_VALUE_FOR_UNKNOWN_MAX_VALUE } from "../../../../utilities"; import { COE_WINDOW, addDays, getISODateOnlyString } from "@sims/utilities"; @@ -35,6 +38,7 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" let offeringRepo: Repository; let collegeC: Institution; let collegeCLocation: InstitutionLocation; + let db: E2EDataSources; beforeAll(async () => { const { nestApplication, dataSource } = await createTestingAppModule(); @@ -43,6 +47,7 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" applicationRepo = dataSource.getRepository(Application); disbursementScheduleRepo = dataSource.getRepository(DisbursementSchedule); offeringRepo = dataSource.getRepository(EducationProgramOffering); + db = createE2EDataSources(dataSource); const { institution } = await getAuthRelatedEntities( appDataSource, @@ -89,6 +94,34 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" ); }); + it("Should not allow the COE confirmation when the user is read-only.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/999999/confirm`; + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .patch(endpoint) + .auth(await collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it("Should allow the second COE confirmation when the application is on Completed status and all the conditions are fulfilled.", async () => { // Arrange const application = await saveFakeApplicationDisbursements( diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts index b06e557e52..2b797c9527 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts @@ -23,6 +23,7 @@ import { DisbursementScheduleStatus, Institution, InstitutionLocation, + InstitutionUserTypes, OfferingIntensity, StudentAssessmentStatus, } from "@sims/sims-db"; @@ -83,6 +84,34 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf expect(declinedCOE.coeDeniedReason.id).toBe(coeDenyReasonId); }); + it("Should not decline the COE when user is read-only.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/9999999/deny`; + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .patch(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it( "Should decline the COE and create reassessment for impacted application(s) " + "when institution decline a COE and the application that belongs to COE being declined can potentially " + diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts index 7281f219aa..69ccc77435 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts @@ -21,7 +21,7 @@ import { COEDeniedReasonService, DisbursementScheduleService, } from "../../services"; -import { DisbursementSchedule } from "@sims/sims-db"; +import { DisbursementSchedule, InstitutionUserTypes } from "@sims/sims-db"; import { getUserFullName } from "../../utilities/auth-utils"; import { getCOEDeniedReason, @@ -245,7 +245,7 @@ export class ConfirmationOfEnrollmentInstitutionsController extends BaseControll * @param locationId location id of the application. * @param payload COE confirmation information. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiNotFoundResponse({ description: "Enrolment not found.", }) @@ -283,7 +283,7 @@ export class ConfirmationOfEnrollmentInstitutionsController extends BaseControll * @param payload contains the denied reason of the * student application. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiNotFoundResponse({ description: "Enrolment not found.", }) diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts index a872ece1d9..5e620c7343 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts @@ -2,6 +2,7 @@ import { HttpStatus, INestApplication } from "@nestjs/common"; import { Institution, InstitutionLocation, + InstitutionUserTypes, OfferingIntensity, OfferingStatus, OfferingTypes, @@ -198,6 +199,33 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", ( ); }); + it("Should not create a new offering when user is read-only.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999`; + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .post(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it("Should throw error when education program is expired.", async () => { // Arrange const fakeEducationProgram = createFakeEducationProgram( diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts index 8a48a2ce93..34e4612d33 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts @@ -2,6 +2,7 @@ import { HttpStatus, INestApplication } from "@nestjs/common"; import { Institution, InstitutionLocation, + InstitutionUserTypes, OfferingIntensity, OfferingStatus, OfferingTypes, @@ -160,6 +161,35 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer ); }); + it("Should not update a new offering when requested by a read-only user.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + + const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999/offering/999999`; + + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .patch(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it("Should throw error when education program is expired.", async () => { // Arrange const fakeEducationProgram = createFakeEducationProgram( diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts index f479436048..8a0e389098 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts @@ -32,7 +32,11 @@ import { UserToken, } from "../../auth/decorators"; import { IInstitutionUserToken } from "../../auth/userToken.interface"; -import { OfferingIntensity, OfferingTypes } from "@sims/sims-db"; +import { + InstitutionUserTypes, + OfferingIntensity, + OfferingTypes, +} from "@sims/sims-db"; import { CreateFromValidatedOfferingError, EducationProgramOfferingService, @@ -101,7 +105,7 @@ export class EducationProgramOfferingInstitutionsController extends BaseControll * @param payload offering data to be validated. * @returns offering validation result. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiNotFoundResponse({ description: "Not able to find the program in the provided location.", }) @@ -159,7 +163,7 @@ export class EducationProgramOfferingInstitutionsController extends BaseControll * @param programId offering program. * @returns primary identifier of the created offering. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiNotFoundResponse({ description: "Program to create the offering not found for the institution or " + @@ -218,7 +222,7 @@ export class EducationProgramOfferingInstitutionsController extends BaseControll * @param programId offering program. * @param offeringId offering to be modified. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiUnprocessableEntityResponse({ description: "Either offering for the program and location is not found " + @@ -296,7 +300,7 @@ export class EducationProgramOfferingInstitutionsController extends BaseControll * @param programId offering program. * @param offeringId offering to be modified. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @ApiUnprocessableEntityResponse({ description: "Either offering for the program and location is not found or the offering is not in the appropriate status to be updated.", @@ -455,7 +459,7 @@ export class EducationProgramOfferingInstitutionsController extends BaseControll * @param programId program to which the offering belongs to. * @returns primary identifier of created resource. */ - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Post( ":offeringId/location/:locationId/education-program/:programId/request-change", ) diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.createEducationProgram.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.createEducationProgram.e2e-spec.ts index 0b5cc9a425..e74f5fa1c9 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.createEducationProgram.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.createEducationProgram.e2e-spec.ts @@ -144,6 +144,25 @@ describe("EducationProgramInstitutionsController(e2e)-createEducationProgram", ( ); }); + it("Should not create an education program when user is read-only.", async () => { + // Arrange + const institutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const endpoint = "/institutions/education-program"; + + // Act/Assert + await request(app.getHttpServer()) + .post(endpoint) + .auth(institutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "You are not authorized to create or modify a program.", + error: "Forbidden", + }); + }); + it("Should throw duplicate SABC code for education program when there is already an active education program with the same SABC code.", async () => { // Arrange const sameSabcCode = "GGG9"; diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.deactivateProgram.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.deactivateProgram.e2e-spec.ts index 3a6503dd92..be88b94419 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.deactivateProgram.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/_tests_/e2e/education-program.institutions.controller.deactivateProgram.e2e-spec.ts @@ -74,6 +74,25 @@ describe("EducationProgramInstitutionsController(e2e)-deactivateProgram", () => }); }); + it("Should not deactivate an education program when user is read-only.", async () => { + // Arrange + const institutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const endpoint = "/institutions/education-program/99999/deactivate"; + + // Act/Assert + await request(app.getHttpServer()) + .patch(endpoint) + .auth(institutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "You are not authorized to create or modify a program.", + error: "Forbidden", + }); + }); + it("Should return a not found HTTP status when a different institution accesses the program.", async () => { // Arrange const program = createFakeEducationProgram({ diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts index 8f5362dfba..5ab2cac912 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, DefaultValuePipe, + ForbiddenException, Get, Param, ParseBoolPipe, @@ -24,12 +25,16 @@ import { } from "./models/education-program.dto"; import { ClientTypeBaseRoute } from "../../types"; import { + ApiForbiddenResponse, ApiNotFoundResponse, ApiTags, ApiUnprocessableEntityResponse, } from "@nestjs/swagger"; import BaseController from "../BaseController"; -import { EducationProgramService } from "../../services"; +import { + EducationProgramService, + InstitutionUserAuthorizations, +} from "../../services"; import { PaginatedResultsAPIOutDTO, ProgramsPaginationOptionsAPIInDTO, @@ -37,6 +42,7 @@ import { import { EducationProgramControllerService } from ".."; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; import { OptionItemAPIOutDTO } from "../models/common.dto"; +import { InstitutionUserTypes } from "@sims/sims-db"; @AllowAuthorizedParty(AuthorizedParties.institution) @Controller("education-program") @@ -79,11 +85,15 @@ export class EducationProgramInstitutionsController extends BaseController { "Not able to a save the program due to an invalid request or " + "duplicate SABC code.", }) + @ApiForbiddenResponse({ + description: "You are not authorized to create or modify a program.", + }) @Post() async createEducationProgram( @Body() payload: EducationProgramAPIInDTO, @UserToken() userToken: IInstitutionUserToken, ): Promise { + this.checkAuthorization(userToken.authorizations); const newProgram = await this.educationProgramControllerService.saveProgram( payload, userToken.authorizations.institutionId, @@ -106,12 +116,16 @@ export class EducationProgramInstitutionsController extends BaseController { @ApiNotFoundResponse({ description: "Not able to find the education program.", }) + @ApiForbiddenResponse({ + description: "You are not authorized to create or modify a program.", + }) @Patch(":programId") async updateEducationProgram( @Param("programId", ParseIntPipe) programId: number, @Body() payload: EducationProgramAPIInDTO, @UserToken() userToken: IInstitutionUserToken, ): Promise { + this.checkAuthorization(userToken.authorizations); await this.educationProgramControllerService.saveProgram( payload, userToken.authorizations.institutionId, @@ -130,11 +144,15 @@ export class EducationProgramInstitutionsController extends BaseController { @ApiUnprocessableEntityResponse({ description: "The education program is already set as requested.", }) + @ApiForbiddenResponse({ + description: "You are not authorized to create or modify a program.", + }) @Patch(":programId/deactivate") async deactivateProgram( @Param("programId", ParseIntPipe) programId: number, @UserToken() userToken: IInstitutionUserToken, ): Promise { + this.checkAuthorization(userToken.authorizations); await this.educationProgramControllerService.deactivateProgram( programId, userToken.userId, @@ -186,4 +204,24 @@ export class EducationProgramInstitutionsController extends BaseController { userToken.authorizations.institutionId, ); } + + /** + * Checks if the user is authorized to create or modify programs for the institution. + * User should have a user type different from read-only user. + * @param userToken + */ + private checkAuthorization( + institutionUserAuthorizations: InstitutionUserAuthorizations, + ) { + const isAuthorized = institutionUserAuthorizations.authorizations.some( + (auth) => + auth.userType === InstitutionUserTypes.admin || + auth.userType === InstitutionUserTypes.user, + ); + if (!isAuthorized) { + throw new ForbiddenException( + "You are not authorized to create or modify a program.", + ); + } + } } diff --git a/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts index 660663f8f0..0e26b89d9d 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts @@ -34,6 +34,7 @@ import { CustomNamedError, getISODateOnlyString } from "@sims/utilities"; import { Application, AssessmentTriggerType, + InstitutionUserTypes, ProgramInfoStatus, } from "@sims/sims-db"; import { @@ -164,7 +165,7 @@ export class ProgramInfoRequestInstitutionsController extends BaseController { description: "'Other' is selected as PIR reason but the reason was not provided.", }) - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Patch(":locationId/program-info-request/application/:applicationId/deny") async denyProgramInfoRequest( @Param("locationId", ParseIntPipe) locationId: number, @@ -220,7 +221,7 @@ export class ProgramInfoRequestInstitutionsController extends BaseController { @ApiUnauthorizedResponse({ description: "The location does not have access to the offering.", }) - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Patch(":locationId/program-info-request/application/:applicationId/complete") async completeProgramInfoRequest( @Param("locationId", ParseIntPipe) locationId: number, diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts index f6c55c2c26..244cd61fd9 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts @@ -21,6 +21,7 @@ import { ApplicationStatus, AssessmentTriggerType, InstitutionLocation, + InstitutionUserTypes, NotificationMessageType, OfferingIntensity, StudentScholasticStandingChangeType, @@ -260,6 +261,34 @@ describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticSt ).toBe(createdScholasticStandingId); }); + it("Should not create a new scholastic standing when the user is read-only.", async () => { + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/institutions/scholastic-standing/location/${collegeELocation.id}/application/99999`; + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .post(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); + it("Should create a new scholastic standing 'School transfer' for a part-time application when the institution user requests.", async () => { // Arrange const application = await saveFakeApplication( diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts index 24ba6644b0..17d4cec917 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts @@ -63,6 +63,7 @@ import { uploadLimits, } from "../../utilities"; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; +import { InstitutionUserTypes } from "@sims/sims-db"; /** * Scholastic standing controller for institutions Client. @@ -95,7 +96,7 @@ export class ScholasticStandingInstitutionsController extends BaseController { "Application not found or invalid application or invalid" + " application status or another assessment already in progress.", }) - @HasLocationAccess("locationId") + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Post("location/:locationId/application/:applicationId") async saveScholasticStanding( @Param("locationId", ParseIntPipe) locationId: number, diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 6f7db6aacd..2659960c3d 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -12,12 +12,15 @@ import { createFakeInstitutionUser } from "@sims/test-utils"; import { COLLEGE_C_BUSINESS_GUID, COLLEGE_D_BUSINESS_GUID, + COLLEGE_E_BUSINESS_GUID, COLLEGE_F_BUSINESS_GUID, SIMS2_COLLC_USER, SIMS2_COLLD_USER, + SIMS2_COLLE_USER, SIMS2_COLLF_USER, SIMS_COLLC_ADMIN_LEGAL_SIGNING_USER, SIMS_COLLD_ADMIN_NON_LEGAL_SIGNING_USER, + SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER, SIMS_COLLF_ADMIN_LEGAL_SIGNING_USER, } from "@sims/test-utils/constants"; import { DataSource, IsNull } from "typeorm"; @@ -53,6 +56,14 @@ export async function getAuthRelatedEntities( businessGuid = COLLEGE_D_BUSINESS_GUID; userName = SIMS2_COLLD_USER; break; + case InstitutionTokenTypes.CollegeEAdminNonLegalSigningUser: + businessGuid = COLLEGE_E_BUSINESS_GUID; + userName = SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER; + break; + case InstitutionTokenTypes.CollegeEReadOnlyUser: + businessGuid = COLLEGE_E_BUSINESS_GUID; + userName = SIMS2_COLLE_USER; + break; case InstitutionTokenTypes.CollegeFAdminLegalSigningUser: businessGuid = COLLEGE_F_BUSINESS_GUID; userName = SIMS_COLLF_ADMIN_LEGAL_SIGNING_USER; @@ -66,7 +77,7 @@ export async function getAuthRelatedEntities( const userRepo = dataSource.getRepository(User); const institution = await institutionRepo.findOneBy({ businessGuid }); const user = await userRepo.findOneBy({ userName }); - return { institution, user }; + return { institution: institution, user }; } /** @@ -123,6 +134,7 @@ export async function authorizeUserTokenForLocation( dataSource: DataSource, userTokenType: InstitutionTokenTypes, location: InstitutionLocation, + institutionUserType?: InstitutionUserTypes, ) { const { institution, user } = await getAuthRelatedEntities( dataSource, @@ -137,7 +149,7 @@ export async function authorizeUserTokenForLocation( institution.id, user.id, location.id, - InstitutionUserTypes.user, + institutionUserType ?? InstitutionUserTypes.user, ); } diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts index 4f21ba31a9..94be3c23aa 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts @@ -1,9 +1,11 @@ import { SIMS2_COLLC_USER, SIMS2_COLLD_USER, + SIMS2_COLLE_USER, SIMS2_COLLF_USER, SIMS_COLLC_ADMIN_LEGAL_SIGNING_USER, SIMS_COLLD_ADMIN_NON_LEGAL_SIGNING_USER, + SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER, SIMS_COLLF_ADMIN_LEGAL_SIGNING_USER, } from "@sims/test-utils/constants"; import { UserPasswordCredential } from "@sims/utilities/config"; @@ -30,6 +32,14 @@ export enum InstitutionTokenTypes { * SIMS2_COLLD user configured as a regular user. */ CollegeDUser, + /** + * SIMS_COLLE user configured as an admin without legal signing officer. + */ + CollegeEAdminNonLegalSigningUser, + /** + * SIMS2_COLLE user configured as a regular user. + */ + CollegeEReadOnlyUser, /** * SIMS_COLLF user configured as an admin and also as a legal signing officer. */ @@ -74,6 +84,18 @@ export async function getInstitutionToken( password: process.env.E2E_TEST_PASSWORD, }; break; + case InstitutionTokenTypes.CollegeEAdminNonLegalSigningUser: + credential = { + userName: SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER, + password: process.env.E2E_TEST_PASSWORD, + }; + break; + case InstitutionTokenTypes.CollegeEReadOnlyUser: + credential = { + userName: SIMS2_COLLE_USER, + password: process.env.E2E_TEST_PASSWORD, + }; + break; case InstitutionTokenTypes.CollegeFAdminLegalSigningUser: credential = { userName: SIMS_COLLF_ADMIN_LEGAL_SIGNING_USER, diff --git a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts index 450d12f518..962e8ef8b9 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts @@ -7,9 +7,11 @@ import { Groups, RequiresStudentAccount, RequiresUserAccount, + HasLocationAccess, } from "../../../auth/decorators"; import { Role } from "../../../auth/roles.enum"; import { UserGroups } from "../../../auth/user-groups.enum"; +import { InstitutionUserTypes } from "@sims/sims-db"; /** * Controller dedicated to test the functionalities around the authentication layer. @@ -117,4 +119,22 @@ export class AuthTestController { userNotRequired(): void { return; } + + /** + * Test route which requires a location to be present as default authorization. + */ + @HasLocationAccess("locationId") + @Get("/institution-location-reading-route/:locationId") + institutionLocationReadingRoute(): void { + return; + } + + /** + * Test route which requires a location to be present and the institution user type should be 'user' (non-read-only). + */ + @HasLocationAccess("locationId", [InstitutionUserTypes.user]) + @Get("/institution-location-modifying-route/:locationId") + institutionLocationModifyingRoute(): void { + return; + } } diff --git a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.model.ts b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.model.ts index 904d1380ab..c3a3a80df4 100644 --- a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.model.ts +++ b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.model.ts @@ -6,12 +6,15 @@ import { import { COLLEGE_C_BUSINESS_GUID, COLLEGE_D_BUSINESS_GUID, + COLLEGE_E_BUSINESS_GUID, COLLEGE_F_BUSINESS_GUID, SIMS2_COLLC_USER, SIMS2_COLLD_USER, + SIMS2_COLLE_USER, SIMS2_COLLF_USER, SIMS_COLLC_ADMIN_LEGAL_SIGNING_USER, SIMS_COLLD_ADMIN_NON_LEGAL_SIGNING_USER, + SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER, SIMS_COLLF_ADMIN_LEGAL_SIGNING_USER, } from "@sims/test-utils/constants"; @@ -48,7 +51,7 @@ export const INSTITUTIONS_INITIAL_DATA: InstitutionBaseData[] = [ { userName: SIMS2_COLLC_USER, firstName: "SIMS2", - lastName: "SIMS2", + lastName: "COLLC", userType: InstitutionUserTypes.user, userRole: undefined, }, @@ -76,6 +79,28 @@ export const INSTITUTIONS_INITIAL_DATA: InstitutionBaseData[] = [ }, ], }, + { + legalOperatingName: "College E - Business BCeID", + operatingName: "College E (non-legal operating name)", + businessGuid: COLLEGE_E_BUSINESS_GUID, + institutionTypeId: INSTITUTION_TYPE_BC_PUBLIC, + users: [ + { + userName: SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER, + firstName: "SIMS", + lastName: "COLLE", + userType: InstitutionUserTypes.admin, + userRole: undefined, + }, + { + userName: SIMS2_COLLE_USER, + firstName: "SIMS2", + lastName: "COLLE", + userType: InstitutionUserTypes.readOnlyUser, + userRole: undefined, + }, + ], + }, { legalOperatingName: "College F - Business BCeID", operatingName: "College F (non-legal operating name)", diff --git a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts index 57df2b76ac..906ff090ce 100644 --- a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts +++ b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts @@ -97,9 +97,12 @@ export class CreateInstitutionsAndAuthenticationUsers { user.userRole, ); // Check if a default location need to be created. - // Only regular users must be associated with a location. + // Only read-only and regular users must be associated with a location. let location: InstitutionLocation; - if (user.userType === InstitutionUserTypes.user) { + if ( + user.userType === InstitutionUserTypes.user || + user.userType === InstitutionUserTypes.readOnlyUser + ) { // Crate a default location. // Create a default location to have it associated with the regular user. const fakeInstitutionDefaultLocation = createFakeInstitutionLocation({ diff --git a/sources/packages/backend/libs/sims-db/src/entities/user-types.enum.ts b/sources/packages/backend/libs/sims-db/src/entities/user-types.enum.ts index 6fd7a5a6b5..ae210a4bda 100644 --- a/sources/packages/backend/libs/sims-db/src/entities/user-types.enum.ts +++ b/sources/packages/backend/libs/sims-db/src/entities/user-types.enum.ts @@ -1,6 +1,7 @@ export enum InstitutionUserTypes { admin = "admin", user = "user", + readOnlyUser = "read-only-user", } export enum InstitutionUserRoles { diff --git a/sources/packages/backend/libs/test-utils/src/constants/institution.constants.ts b/sources/packages/backend/libs/test-utils/src/constants/institution.constants.ts index bb93f44656..7e64d3960a 100644 --- a/sources/packages/backend/libs/test-utils/src/constants/institution.constants.ts +++ b/sources/packages/backend/libs/test-utils/src/constants/institution.constants.ts @@ -25,6 +25,19 @@ export const SIMS2_COLLD_USER = "2260473f105e4a54bad8c7e348cd9b82@bceidboth"; * College D business guid. */ export const COLLEGE_D_BUSINESS_GUID = "B0BF341C26164156AD486F274108539A"; +/** + * SIMS_COLLE user configured as an admin without legal signing officer. + */ +export const SIMS_COLLE_ADMIN_NON_LEGAL_SIGNING_USER = + "a0e8119fe7e4490397f6de7471261e69@bceidboth"; +/** + * SIMS2_COLLE user configured as a read-only user. + */ +export const SIMS2_COLLE_USER = "954adea3f4fc49db8085082ba60fb575@bceidboth"; +/** + * College E business guid. + */ +export const COLLEGE_E_BUSINESS_GUID = "C65360FBB47B45C6BCA7B8CCCB4F5BCF"; /** * SIMS_COLLF user configured as an admin and also as a legal signing officer. */ From befa7a77692870b9dd93652583487379f7cdc6fc Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 11:34:06 -0800 Subject: [PATCH 02/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 102 +++++++++--------- ...institutions.confirmEnrollment.e2e-spec.ts | 2 +- .../auth/institution-token-helpers.ts | 3 +- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 1b15dd49ea..5a0e84c7cb 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -302,58 +302,58 @@ describe("Authentication (e2e)", () => { }, ); - it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-reading-route/${collegeELocation.id}`, - ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }); + // it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.OK); + // }); - it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, - ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); - }); - }); + // it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.FORBIDDEN); + // }); + // }); afterAll(async () => { await app.close(); diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts index 7d2a21084f..5b881f7ecc 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts @@ -170,7 +170,7 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" // Act/Assert await request(app.getHttpServer()) .patch(endpoint) - .auth(await collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.FORBIDDEN); }); diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts index 94be3c23aa..8b34e17b61 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-token-helpers.ts @@ -37,7 +37,8 @@ export enum InstitutionTokenTypes { */ CollegeEAdminNonLegalSigningUser, /** - * SIMS2_COLLE user configured as a regular user. + * SIMS2_COLLE user configured as a read-only user. + * Please do not add user type 'user' to this user as it is intended to be used as a read-only user. */ CollegeEReadOnlyUser, /** From f0739edd3109f77dbab8bdc569035b8b82586fa8 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 11:46:18 -0800 Subject: [PATCH 03/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 109 +++++++++--------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 5a0e84c7cb..3a29e7090f 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,7 +9,6 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, - createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -22,16 +21,12 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { - authorizeUserTokenForLocation, BEARER_AUTH_TYPE, - getAuthRelatedEntities, - getInstitutionToken, - InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; +import { Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -302,58 +297,58 @@ describe("Authentication (e2e)", () => { }, ); - // it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.OK); - // }); + // it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.OK); + // }); - // it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.FORBIDDEN); - // }); - // }); + // it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.FORBIDDEN); + // }); + }); afterAll(async () => { await app.close(); From bb0a0087b50425ef315c251c3a63938f04d44a39 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 12:09:26 -0800 Subject: [PATCH 04/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 107 +++++++++--------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 3a29e7090f..1b15dd49ea 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,6 +9,7 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, + createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -21,12 +22,16 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { + authorizeUserTokenForLocation, BEARER_AUTH_TYPE, + getAuthRelatedEntities, + getInstitutionToken, + InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { Student, User } from "@sims/sims-db"; +import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -297,57 +302,57 @@ describe("Authentication (e2e)", () => { }, ); - // it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.OK); - // }); + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }); - // it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.FORBIDDEN); - // }); + it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); + }); }); afterAll(async () => { From 4afbf19448d9af3002218bd731b4e25f3feb94dd Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 12:27:41 -0800 Subject: [PATCH 05/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 1b15dd49ea..957053cfd7 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -302,7 +302,7 @@ describe("Authentication (e2e)", () => { }, ); - it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a reading route to their institution.", async () => { + it.skip("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, @@ -328,7 +328,7 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }); - it("Should return a HttpStatus OK(200) when a non-read-only institution user tries to access a route to their institution.", async () => { + it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, From 6590133b991f9bf32a7950bb927a4d4f440ae050 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 13:24:22 -0800 Subject: [PATCH 06/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 957053cfd7..9f9264647c 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -302,7 +302,7 @@ describe("Authentication (e2e)", () => { }, ); - it.skip("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, @@ -328,7 +328,7 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }); - it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + it.skip("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, From 5f2b21e7349cd08b18db6edc2f706a1cbdb5b849 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 13:55:08 -0800 Subject: [PATCH 07/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 57 +++++++------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 9f9264647c..23ff018f21 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,7 +9,6 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, - createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -22,16 +21,15 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { - authorizeUserTokenForLocation, BEARER_AUTH_TYPE, - getAuthRelatedEntities, getInstitutionToken, InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; +import { Student, User } from "@sims/sims-db"; +import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -303,54 +301,37 @@ describe("Authentication (e2e)", () => { ); it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, + const institutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, + const institutionUserAuth = await db.institutionUserAuth.findOne({ + where: { + institutionUser: { user: { userName: SIMS2_COLLE_USER } }, + }, + relations: { location: true }, }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); return request(app.getHttpServer()) .get( - `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + `/auth-test/institution-location-reading-route/${institutionUserAuth.location.id}`, ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .auth(institutionUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.OK); }); - - it.skip("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, + it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + const institutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, + const institutionUserAuth = await db.institutionUserAuth.findOne({ + where: { + institutionUser: { user: { userName: SIMS2_COLLE_USER } }, + }, + relations: { location: true }, }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); return request(app.getHttpServer()) .get( - `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + `/auth-test/institution-location-modifying-route/${institutionUserAuth.location.id}`, ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .auth(institutionUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.FORBIDDEN); }); }); From faa8138f859cba0acbd2839a4a32c3a39825c4a8 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 14:06:31 -0800 Subject: [PATCH 08/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 445 +++++++++--------- 1 file changed, 231 insertions(+), 214 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 23ff018f21..954b4b138f 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,6 +9,7 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, + createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -21,15 +22,16 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { + authorizeUserTokenForLocation, BEARER_AUTH_TYPE, + getAuthRelatedEntities, getInstitutionToken, InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { Student, User } from "@sims/sims-db"; -import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; +import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -93,248 +95,263 @@ describe("Authentication (e2e)", () => { await resetMockUserLoginInfo(moduleFixture); }); - it("Load publicKey from Keycloak", async () => { - // Arrange - const headerAndFooterLength = - PEM_BEGIN_HEADER.length + PEM_END_HEADER.length; - - // Act - await KeycloakConfig.load(); + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }); - // Assert - expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_BEGIN_HEADER); - expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_END_HEADER); - // Besides that header and footer, the public_key need have some additional - // content that would be the public key retrieve fromKeycloak, - // that does not contains the PEM_BEGIN_HEADER and PEM_END_HEADER. - expect(KeycloakConfig.PEM_PublicKey.length).toBeGreaterThan( - headerAndFooterLength, + it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, ); + // Institution token. + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + return request(app.getHttpServer()) + .get( + `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + ) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); }); +}); - it( - "Should allow BCSC user to access the application when user is not registered in beta users authorizations table but " + - "config allows any user to access the application.", - async () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }, - ); +it("Load publicKey from Keycloak", async () => { + // Arrange + const headerAndFooterLength = PEM_BEGIN_HEADER.length + PEM_END_HEADER.length; - it( - "Should not allow BCSC beta user to access the application when config allows only beta users to access the application but " + - "user is not registered in beta users authorizations table.", - async () => { - // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.UNAUTHORIZED) - .expect({ - message: "The student is not registered as a beta user.", - errorType: INVALID_BETA_USER, - }); - }, + // Act + await KeycloakConfig.load(); + + // Assert + expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_BEGIN_HEADER); + expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_END_HEADER); + // Besides that header and footer, the public_key need have some additional + // content that would be the public key retrieve fromKeycloak, + // that does not contains the PEM_BEGIN_HEADER and PEM_END_HEADER. + expect(KeycloakConfig.PEM_PublicKey.length).toBeGreaterThan( + headerAndFooterLength, ); +}); - it( - "Should allow BCSC beta user to access the application when config allows only beta users to access the application and " + - "user is registered in beta users authorizations table.", - async () => { - // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); +it( + "Should allow BCSC user to access the application when user is not registered in beta users authorizations table but " + + "config allows any user to access the application.", + async () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }, +); - // Add user to beta users authorizations table in upper case to test the query. - await db.betaUsersAuthorizations.save({ - givenNames: studentDecodedToken.givenNames.toUpperCase(), - lastName: studentDecodedToken.lastName.toUpperCase(), +it( + "Should not allow BCSC beta user to access the application when config allows only beta users to access the application but " + + "user is not registered in beta users authorizations table.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); + // Act/Assert + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + message: "The student is not registered as a beta user.", + errorType: INVALID_BETA_USER, }); + }, +); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }, - ); +it( + "Should allow BCSC beta user to access the application when config allows only beta users to access the application and " + + "user is registered in beta users authorizations table.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); - it( - "Should not allow BCSC beta user to access the application when config allows only beta users to access the application and " + - "user is registered in beta users authorizations table with a future date to be enabled.", - async () => { - // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); + // Add user to beta users authorizations table in upper case to test the query. + await db.betaUsersAuthorizations.save({ + givenNames: studentDecodedToken.givenNames.toUpperCase(), + lastName: studentDecodedToken.lastName.toUpperCase(), + }); - const tomorrow = dayjs().add(1, "day"); - const betaUsersAuthorizations = - await db.betaUsersAuthorizations.findOneBy({ - givenNames: studentDecodedToken.givenNames.toUpperCase(), - lastName: studentDecodedToken.lastName.toUpperCase(), - }); - betaUsersAuthorizations.enabledFrom = tomorrow.toDate(); - // Save beta users authorizations with tomorrow's date. - await db.betaUsersAuthorizations.save(betaUsersAuthorizations); + // Act/Assert + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }, +); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.UNAUTHORIZED) - .expect({ - message: "The student is not registered as a beta user.", - errorType: INVALID_BETA_USER, - }); - }, - ); +it( + "Should not allow BCSC beta user to access the application when config allows only beta users to access the application and " + + "user is registered in beta users authorizations table with a future date to be enabled.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); - it("Endpoint with Public decorator should allow access when the bearer token is not present", () => { + const tomorrow = dayjs().add(1, "day"); + const betaUsersAuthorizations = await db.betaUsersAuthorizations.findOneBy({ + givenNames: studentDecodedToken.givenNames.toUpperCase(), + lastName: studentDecodedToken.lastName.toUpperCase(), + }); + betaUsersAuthorizations.enabledFrom = tomorrow.toDate(); + // Save beta users authorizations with tomorrow's date. + await db.betaUsersAuthorizations.save(betaUsersAuthorizations); + + // Act/Assert return request(app.getHttpServer()) - .get("/auth-test/public-route") - .expect(HttpStatus.OK); + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + message: "The student is not registered as a beta user.", + errorType: INVALID_BETA_USER, + }); + }, +); + +it("Endpoint with Public decorator should allow access when the bearer token is not present", () => { + return request(app.getHttpServer()) + .get("/auth-test/public-route") + .expect(HttpStatus.OK); +}); + +describe("Endpoint that requires authentication", () => { + it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is not present", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .expect(HttpStatus.UNAUTHORIZED); }); - describe("Endpoint that requires authentication", () => { - it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is not present", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .expect(HttpStatus.UNAUTHORIZED); - }); + it("Should return a HttpStatus OK(200) when bearer token is present", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }); - it("Should return a HttpStatus OK(200) when bearer token is present", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }); + it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is present but it is invalid", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth("invalid_token", { type: "bearer" }) + .expect(HttpStatus.UNAUTHORIZED); + }); - it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is present but it is invalid", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .auth("invalid_token", { type: "bearer" }) - .expect(HttpStatus.UNAUTHORIZED); - }); + it("Should return a HttpStatus OK(200) when the Role decorator is present and the role is present and it is the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-role") + .auth(aestAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }); - it("Should return a HttpStatus OK(200) when the Role decorator is present and the role is present and it is the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-role") - .auth(aestAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }); + it("Should return a HttpStatus FORBIDDEN(403) when the Role decorator is present but the role it is not the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-non-existing-role") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.FORBIDDEN); + }); - it("Should return a HttpStatus FORBIDDEN(403) when the Role decorator is present but the role it is not the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-non-existing-role") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN); - }); + it("Should return a HttpStatus OK(200) when the Group decorator is present and the group is present and it is the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-group") + .auth(aestAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }); - it("Should return a HttpStatus OK(200) when the Group decorator is present and the group is present and it is the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-group") - .auth(aestAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }); + it("Should return a HttpStatus FORBIDDEN(403) when the Group decorator is present but the group it is not the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-non-existing-group") + .auth(aestAccessToken, { type: "bearer" }) + .expect(HttpStatus.FORBIDDEN); + }); - it("Should return a HttpStatus FORBIDDEN(403) when the Group decorator is present but the group it is not the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-non-existing-group") - .auth(aestAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN); - }); + it("Can parse the UserToken", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK) + .then((resp) => { + // Only the basic properties that are present in a basic + // Keycloak user are being validated here. + expect(resp.body).toBeDefined(); + expect(resp.body.userName).toBeTruthy(); + expect(resp.body.email).toBeTruthy(); + }); + }); - it("Can parse the UserToken", () => { + it( + "Should return a HttpStatus FORBIDDEN(403) when there is no user account associated to the user token " + + "to a default route that requires a user account.", + async () => { + await mockUserLoginInfo(moduleFixture, { + id: null, + user: { id: null, isActive: null } as User, + } as Student); return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") + .get("/auth-test/default-requires-user-route") .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK) - .then((resp) => { - // Only the basic properties that are present in a basic - // Keycloak user are being validated here. - expect(resp.body).toBeDefined(); - expect(resp.body.userName).toBeTruthy(); - expect(resp.body.email).toBeTruthy(); + .expect(HttpStatus.FORBIDDEN) + .expect({ + message: "No user account has been associated to the user token.", + errorType: MISSING_USER_ACCOUNT, }); - }); - - it( - "Should return a HttpStatus FORBIDDEN(403) when there is no user account associated to the user token " + - "to a default route that requires a user account.", - async () => { - await mockUserLoginInfo(moduleFixture, { - id: null, - user: { id: null, isActive: null } as User, - } as Student); - return request(app.getHttpServer()) - .get("/auth-test/default-requires-user-route") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN) - .expect({ - message: "No user account has been associated to the user token.", - errorType: MISSING_USER_ACCOUNT, - }); - }, - ); - - it( - "Should return a HttpStatus OK(200) when there is no user account associated to the user token " + - "to a route that does not requires a user account.", - async () => { - await mockUserLoginInfo(moduleFixture, { - id: null, - user: { id: null, isActive: null } as User, - } as Student); - return request(app.getHttpServer()) - .get("/auth-test/user-not-required-route") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }, - ); + }, + ); - it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { - const institutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const institutionUserAuth = await db.institutionUserAuth.findOne({ - where: { - institutionUser: { user: { userName: SIMS2_COLLE_USER } }, - }, - relations: { location: true }, - }); + it( + "Should return a HttpStatus OK(200) when there is no user account associated to the user token " + + "to a route that does not requires a user account.", + async () => { + await mockUserLoginInfo(moduleFixture, { + id: null, + user: { id: null, isActive: null } as User, + } as Student); return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-reading-route/${institutionUserAuth.location.id}`, - ) - .auth(institutionUserToken, BEARER_AUTH_TYPE) + .get("/auth-test/user-not-required-route") + .auth(studentAccessToken, { type: "bearer" }) .expect(HttpStatus.OK); - }); - it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { - const institutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const institutionUserAuth = await db.institutionUserAuth.findOne({ - where: { - institutionUser: { user: { userName: SIMS2_COLLE_USER } }, - }, - relations: { location: true }, - }); - return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-modifying-route/${institutionUserAuth.location.id}`, - ) - .auth(institutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); - }); - }); + }, + ); afterAll(async () => { await app.close(); From f8bbc93cf70ba18e9949dc42528a3746659a1cd2 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 14:09:38 -0800 Subject: [PATCH 09/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 360 +++++++++--------- 1 file changed, 181 insertions(+), 179 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 954b4b138f..b20f9f1adc 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -146,212 +146,214 @@ describe("Authentication (e2e)", () => { .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) .expect(HttpStatus.FORBIDDEN); }); -}); - -it("Load publicKey from Keycloak", async () => { - // Arrange - const headerAndFooterLength = PEM_BEGIN_HEADER.length + PEM_END_HEADER.length; - - // Act - await KeycloakConfig.load(); - - // Assert - expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_BEGIN_HEADER); - expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_END_HEADER); - // Besides that header and footer, the public_key need have some additional - // content that would be the public key retrieve fromKeycloak, - // that does not contains the PEM_BEGIN_HEADER and PEM_END_HEADER. - expect(KeycloakConfig.PEM_PublicKey.length).toBeGreaterThan( - headerAndFooterLength, - ); -}); - -it( - "Should allow BCSC user to access the application when user is not registered in beta users authorizations table but " + - "config allows any user to access the application.", - async () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }, -); -it( - "Should not allow BCSC beta user to access the application when config allows only beta users to access the application but " + - "user is not registered in beta users authorizations table.", - async () => { + it("Load publicKey from Keycloak", async () => { // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.UNAUTHORIZED) - .expect({ - message: "The student is not registered as a beta user.", - errorType: INVALID_BETA_USER, - }); - }, -); + const headerAndFooterLength = + PEM_BEGIN_HEADER.length + PEM_END_HEADER.length; -it( - "Should allow BCSC beta user to access the application when config allows only beta users to access the application and " + - "user is registered in beta users authorizations table.", - async () => { - // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); + // Act + await KeycloakConfig.load(); - // Add user to beta users authorizations table in upper case to test the query. - await db.betaUsersAuthorizations.save({ - givenNames: studentDecodedToken.givenNames.toUpperCase(), - lastName: studentDecodedToken.lastName.toUpperCase(), - }); + // Assert + expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_BEGIN_HEADER); + expect(KeycloakConfig.PEM_PublicKey).toContain(PEM_END_HEADER); + // Besides that header and footer, the public_key need have some additional + // content that would be the public key retrieve fromKeycloak, + // that does not contains the PEM_BEGIN_HEADER and PEM_END_HEADER. + expect(KeycloakConfig.PEM_PublicKey.length).toBeGreaterThan( + headerAndFooterLength, + ); + }); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }, -); + it( + "Should allow BCSC user to access the application when user is not registered in beta users authorizations table but " + + "config allows any user to access the application.", + async () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }, + ); -it( - "Should not allow BCSC beta user to access the application when config allows only beta users to access the application and " + - "user is registered in beta users authorizations table with a future date to be enabled.", - async () => { - // Arrange - jest - .spyOn(configService, "allowBetaUsersOnly", "get") - .mockReturnValue(true); + it( + "Should not allow BCSC beta user to access the application when config allows only beta users to access the application but " + + "user is not registered in beta users authorizations table.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); + // Act/Assert + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + message: "The student is not registered as a beta user.", + errorType: INVALID_BETA_USER, + }); + }, + ); - const tomorrow = dayjs().add(1, "day"); - const betaUsersAuthorizations = await db.betaUsersAuthorizations.findOneBy({ - givenNames: studentDecodedToken.givenNames.toUpperCase(), - lastName: studentDecodedToken.lastName.toUpperCase(), - }); - betaUsersAuthorizations.enabledFrom = tomorrow.toDate(); - // Save beta users authorizations with tomorrow's date. - await db.betaUsersAuthorizations.save(betaUsersAuthorizations); + it( + "Should allow BCSC beta user to access the application when config allows only beta users to access the application and " + + "user is registered in beta users authorizations table.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); - // Act/Assert - return request(app.getHttpServer()) - .get("/auth-test/authenticated-student") - .auth(studentAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.UNAUTHORIZED) - .expect({ - message: "The student is not registered as a beta user.", - errorType: INVALID_BETA_USER, + // Add user to beta users authorizations table in upper case to test the query. + await db.betaUsersAuthorizations.save({ + givenNames: studentDecodedToken.givenNames.toUpperCase(), + lastName: studentDecodedToken.lastName.toUpperCase(), }); - }, -); -it("Endpoint with Public decorator should allow access when the bearer token is not present", () => { - return request(app.getHttpServer()) - .get("/auth-test/public-route") - .expect(HttpStatus.OK); -}); + // Act/Assert + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); + }, + ); -describe("Endpoint that requires authentication", () => { - it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is not present", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .expect(HttpStatus.UNAUTHORIZED); - }); + it( + "Should not allow BCSC beta user to access the application when config allows only beta users to access the application and " + + "user is registered in beta users authorizations table with a future date to be enabled.", + async () => { + // Arrange + jest + .spyOn(configService, "allowBetaUsersOnly", "get") + .mockReturnValue(true); - it("Should return a HttpStatus OK(200) when bearer token is present", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }); + const tomorrow = dayjs().add(1, "day"); + const betaUsersAuthorizations = + await db.betaUsersAuthorizations.findOneBy({ + givenNames: studentDecodedToken.givenNames.toUpperCase(), + lastName: studentDecodedToken.lastName.toUpperCase(), + }); + betaUsersAuthorizations.enabledFrom = tomorrow.toDate(); + // Save beta users authorizations with tomorrow's date. + await db.betaUsersAuthorizations.save(betaUsersAuthorizations); - it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is present but it is invalid", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .auth("invalid_token", { type: "bearer" }) - .expect(HttpStatus.UNAUTHORIZED); - }); + // Act/Assert + return request(app.getHttpServer()) + .get("/auth-test/authenticated-student") + .auth(studentAccessToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + message: "The student is not registered as a beta user.", + errorType: INVALID_BETA_USER, + }); + }, + ); - it("Should return a HttpStatus OK(200) when the Role decorator is present and the role is present and it is the expected one", () => { + it("Endpoint with Public decorator should allow access when the bearer token is not present", () => { return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-role") - .auth(aestAccessToken, { type: "bearer" }) + .get("/auth-test/public-route") .expect(HttpStatus.OK); }); - it("Should return a HttpStatus FORBIDDEN(403) when the Role decorator is present but the role it is not the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-non-existing-role") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN); - }); + describe("Endpoint that requires authentication", () => { + it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is not present", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .expect(HttpStatus.UNAUTHORIZED); + }); - it("Should return a HttpStatus OK(200) when the Group decorator is present and the group is present and it is the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-group") - .auth(aestAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK); - }); + it("Should return a HttpStatus OK(200) when bearer token is present", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }); - it("Should return a HttpStatus FORBIDDEN(403) when the Group decorator is present but the group it is not the expected one", () => { - return request(app.getHttpServer()) - .get("/auth-test/authenticated-route-by-non-existing-group") - .auth(aestAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN); - }); + it("Should return a HttpStatus UNAUTHORIZED(401) when bearer token is present but it is invalid", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth("invalid_token", { type: "bearer" }) + .expect(HttpStatus.UNAUTHORIZED); + }); - it("Can parse the UserToken", () => { - return request(app.getHttpServer()) - .get("/auth-test/global-authenticated-route") - .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.OK) - .then((resp) => { - // Only the basic properties that are present in a basic - // Keycloak user are being validated here. - expect(resp.body).toBeDefined(); - expect(resp.body.userName).toBeTruthy(); - expect(resp.body.email).toBeTruthy(); - }); - }); + it("Should return a HttpStatus OK(200) when the Role decorator is present and the role is present and it is the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-role") + .auth(aestAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }); - it( - "Should return a HttpStatus FORBIDDEN(403) when there is no user account associated to the user token " + - "to a default route that requires a user account.", - async () => { - await mockUserLoginInfo(moduleFixture, { - id: null, - user: { id: null, isActive: null } as User, - } as Student); + it("Should return a HttpStatus FORBIDDEN(403) when the Role decorator is present but the role it is not the expected one", () => { return request(app.getHttpServer()) - .get("/auth-test/default-requires-user-route") + .get("/auth-test/authenticated-route-by-non-existing-role") .auth(studentAccessToken, { type: "bearer" }) - .expect(HttpStatus.FORBIDDEN) - .expect({ - message: "No user account has been associated to the user token.", - errorType: MISSING_USER_ACCOUNT, - }); - }, - ); + .expect(HttpStatus.FORBIDDEN); + }); - it( - "Should return a HttpStatus OK(200) when there is no user account associated to the user token " + - "to a route that does not requires a user account.", - async () => { - await mockUserLoginInfo(moduleFixture, { - id: null, - user: { id: null, isActive: null } as User, - } as Student); + it("Should return a HttpStatus OK(200) when the Group decorator is present and the group is present and it is the expected one", () => { return request(app.getHttpServer()) - .get("/auth-test/user-not-required-route") - .auth(studentAccessToken, { type: "bearer" }) + .get("/auth-test/authenticated-route-by-group") + .auth(aestAccessToken, { type: "bearer" }) .expect(HttpStatus.OK); - }, - ); + }); + + it("Should return a HttpStatus FORBIDDEN(403) when the Group decorator is present but the group it is not the expected one", () => { + return request(app.getHttpServer()) + .get("/auth-test/authenticated-route-by-non-existing-group") + .auth(aestAccessToken, { type: "bearer" }) + .expect(HttpStatus.FORBIDDEN); + }); + + it("Can parse the UserToken", () => { + return request(app.getHttpServer()) + .get("/auth-test/global-authenticated-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK) + .then((resp) => { + // Only the basic properties that are present in a basic + // Keycloak user are being validated here. + expect(resp.body).toBeDefined(); + expect(resp.body.userName).toBeTruthy(); + expect(resp.body.email).toBeTruthy(); + }); + }); + + it( + "Should return a HttpStatus FORBIDDEN(403) when there is no user account associated to the user token " + + "to a default route that requires a user account.", + async () => { + await mockUserLoginInfo(moduleFixture, { + id: null, + user: { id: null, isActive: null } as User, + } as Student); + return request(app.getHttpServer()) + .get("/auth-test/default-requires-user-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.FORBIDDEN) + .expect({ + message: "No user account has been associated to the user token.", + errorType: MISSING_USER_ACCOUNT, + }); + }, + ); + + it( + "Should return a HttpStatus OK(200) when there is no user account associated to the user token " + + "to a route that does not requires a user account.", + async () => { + await mockUserLoginInfo(moduleFixture, { + id: null, + user: { id: null, isActive: null } as User, + } as Student); + return request(app.getHttpServer()) + .get("/auth-test/user-not-required-route") + .auth(studentAccessToken, { type: "bearer" }) + .expect(HttpStatus.OK); + }, + ); + }); afterAll(async () => { await app.close(); From 113fc5a271b4147652c91d6d6f5815024d4ab0fd Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 14:21:42 -0800 Subject: [PATCH 10/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 111 +++++++++--------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index b20f9f1adc..5e60b24b51 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,7 +9,6 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, - createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -22,16 +21,12 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { - authorizeUserTokenForLocation, BEARER_AUTH_TYPE, - getAuthRelatedEntities, - getInstitutionToken, - InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; +import { Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -95,58 +90,6 @@ describe("Authentication (e2e)", () => { await resetMockUserLoginInfo(moduleFixture); }); - it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-reading-route/${collegeELocation.id}`, - ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.OK); - }); - - it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - // Institution token. - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - return request(app.getHttpServer()) - .get( - `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, - ) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); - }); - it("Load publicKey from Keycloak", async () => { // Arrange const headerAndFooterLength = @@ -353,6 +296,58 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }, ); + + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.OK); + }); + + it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + // const { institution: collegeE } = await getAuthRelatedEntities( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // const collegeELocation = createFakeInstitutionLocation({ + // institution: collegeE, + // }); + // await authorizeUserTokenForLocation( + // db.dataSource, + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // collegeELocation, + // InstitutionUserTypes.readOnlyUser, + // ); + // // Institution token. + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); + // return request(app.getHttpServer()) + // .get( + // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, + // ) + // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + // .expect(HttpStatus.FORBIDDEN); + }); }); afterAll(async () => { From 3428dc96616a62fddc23585b947df8c93c9bac2d Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 14:37:21 -0800 Subject: [PATCH 11/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 5e60b24b51..b831102fc9 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,6 +9,7 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, + createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -21,12 +22,16 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { + authorizeUserTokenForLocation, BEARER_AUTH_TYPE, + getAuthRelatedEntities, + getInstitutionToken, + InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { Student, User } from "@sims/sims-db"; +import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -298,55 +303,57 @@ describe("Authentication (e2e)", () => { ); it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-reading-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.OK); + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .get(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.OK); }); it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { - // const { institution: collegeE } = await getAuthRelatedEntities( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // const collegeELocation = createFakeInstitutionLocation({ - // institution: collegeE, - // }); - // await authorizeUserTokenForLocation( - // db.dataSource, - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // collegeELocation, - // InstitutionUserTypes.readOnlyUser, - // ); - // // Institution token. - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - // return request(app.getHttpServer()) - // .get( - // `/auth-test/institution-location-modifying-route/${collegeELocation.id}`, - // ) - // .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - // .expect(HttpStatus.FORBIDDEN); + // Arrange + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + InstitutionUserTypes.readOnlyUser, + ); + const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; + const collegEInstitutionUserToken = await getInstitutionToken( + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + + // Act/Assert + await request(app.getHttpServer()) + .get(endpoint) + .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .expect(HttpStatus.FORBIDDEN); }); }); From f88441c75ded081767e71513b1a09ebc4332a257 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 15:30:25 -0800 Subject: [PATCH 12/29] checking GHA tests failing --- .../apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 9 +++++++-- sources/tests/Makefile | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index b831102fc9..95f0c6f28f 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -329,15 +329,19 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }); - it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + it.only("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange + + console.log("Before getAuthRelatedEntities !#@$!%$&"); const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, ); + console.log("Before createFakeInstitutionLocation !#$%%^%*&(*"); const collegeELocation = createFakeInstitutionLocation({ institution: collegeE, }); + console.log("Before authorizeUserTokenForLocation !#$%%^%*&(*"); await authorizeUserTokenForLocation( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, @@ -345,10 +349,11 @@ describe("Authentication (e2e)", () => { InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; + console.log("Before getInstitutionToken !#$%%^%*&(*"); const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); - + console.log("Before request !#$%%^%*&(*"); // Act/Assert await request(app.getHttpServer()) .get(endpoint) diff --git a/sources/tests/Makefile b/sources/tests/Makefile index 82164df95b..c4534bba7d 100644 --- a/sources/tests/Makefile +++ b/sources/tests/Makefile @@ -21,7 +21,7 @@ backend-unit-tests: e2e-tests-api: @echo "+\n++ Make: Starting containers for API E2E tests \n+" - @docker compose run --name api-e2e-$(BUILD_REF) api npm run test:e2e:api + @docker compose run --name api-e2e-$(BUILD_REF) api npm run test:e2e:api auth.e2e-spec.ts @mkdir -p coverage/api @docker cp api-e2e-$(BUILD_REF):app/apps/api/coverage/clover.xml coverage/api @docker rm api-e2e-$(BUILD_REF) From b0e6c651a7d12902294d017a3203720a8079eb8f Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 15:47:33 -0800 Subject: [PATCH 13/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 95f0c6f28f..a93fa6c1e3 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -16,7 +16,6 @@ import { import { ConfigModule, ConfigService } from "@sims/utilities/config"; import { createZeebeModuleMock } from "@sims/test-utils/mocks"; import { AppModule } from "../../../app.module"; -import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller"; import { DiscoveryModule } from "@golevelup/nestjs-discovery"; import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; @@ -72,7 +71,7 @@ describe("Authentication (e2e)", () => { imports: [AppModule, createZeebeModuleMock(), DiscoveryModule], // AuthTestController is used only for e2e tests and could be // changed as needed to implement more test scenarios. - controllers: [AuthTestController], + //controllers: [AuthTestController], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); From d758af1623339977781a7be24faa5504602bfa51 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 15:54:42 -0800 Subject: [PATCH 14/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index a93fa6c1e3..91f8924471 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -340,6 +340,7 @@ describe("Authentication (e2e)", () => { const collegeELocation = createFakeInstitutionLocation({ institution: collegeE, }); + console.log("collegeELocation:", collegeELocation); console.log("Before authorizeUserTokenForLocation !#$%%^%*&(*"); await authorizeUserTokenForLocation( db.dataSource, @@ -352,6 +353,10 @@ describe("Authentication (e2e)", () => { const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); + console.log( + "collegEInstitutionUserToken:", + collegEInstitutionUserToken.substring(0, 10), + ); console.log("Before request !#$%%^%*&(*"); // Act/Assert await request(app.getHttpServer()) From 01a8e7bf2e61b2b2ac18f935f4525e49be29e388 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:01:00 -0800 Subject: [PATCH 15/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 91f8924471..3fd97f6f35 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -24,13 +24,14 @@ import { authorizeUserTokenForLocation, BEARER_AUTH_TYPE, getAuthRelatedEntities, - getInstitutionToken, InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; +import { AuthTestController } from "apps/api/src/testHelpers/controllers/auth-test/auth-test.controller"; +import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -48,6 +49,7 @@ describe("Authentication (e2e)", () => { let db: E2EDataSources; let studentDecodedToken: any; let moduleFixture: TestingModule; + let collegEInstitutionUserToken: string; beforeAll(async () => { await KeycloakConfig.load(); @@ -67,11 +69,17 @@ describe("Authentication (e2e)", () => { ); aestAccessToken = aestToken.access_token; + collegEInstitutionUserToken = await KeycloakService.shared.getToken( + SIMS2_COLLE_USER, + process.env.E2E_TEST_STUDENT_PASSWORD, + "institution", + ); + moduleFixture = await Test.createTestingModule({ imports: [AppModule, createZeebeModuleMock(), DiscoveryModule], // AuthTestController is used only for e2e tests and could be // changed as needed to implement more test scenarios. - //controllers: [AuthTestController], + controllers: [AuthTestController], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); @@ -317,9 +325,9 @@ describe("Authentication (e2e)", () => { InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); // Act/Assert await request(app.getHttpServer()) @@ -349,10 +357,10 @@ describe("Authentication (e2e)", () => { InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; - console.log("Before getInstitutionToken !#$%%^%*&(*"); - const collegEInstitutionUserToken = await getInstitutionToken( - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); + // console.log("Before getInstitutionToken !#$%%^%*&(*"); + // const collegEInstitutionUserToken = await getInstitutionToken( + // InstitutionTokenTypes.CollegeEReadOnlyUser, + // ); console.log( "collegEInstitutionUserToken:", collegEInstitutionUserToken.substring(0, 10), From 3f904b2daccc9aae6dbbbfb561d741bf531e0202 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:05:21 -0800 Subject: [PATCH 16/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 3fd97f6f35..c41c29b8d5 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -69,11 +69,12 @@ describe("Authentication (e2e)", () => { ); aestAccessToken = aestToken.access_token; - collegEInstitutionUserToken = await KeycloakService.shared.getToken( + const collegEToken = await KeycloakService.shared.getToken( SIMS2_COLLE_USER, process.env.E2E_TEST_STUDENT_PASSWORD, "institution", ); + collegEInstitutionUserToken = collegEToken.access_token; moduleFixture = await Test.createTestingModule({ imports: [AppModule, createZeebeModuleMock(), DiscoveryModule], From 807281b38a73afbacfcdb27698e6d25456709eff Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:08:32 -0800 Subject: [PATCH 17/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index c41c29b8d5..e96f626b7a 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -30,7 +30,7 @@ import { } from "../../../testHelpers"; import * as dayjs from "dayjs"; import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; -import { AuthTestController } from "apps/api/src/testHelpers/controllers/auth-test/auth-test.controller"; +import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller"; import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; describe("Authentication (e2e)", () => { From 671cf4f41557daea18b4192f9834b80d9efe514b Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:16:31 -0800 Subject: [PATCH 18/29] checking GHA tests failing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index e96f626b7a..adf092455d 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -71,7 +71,7 @@ describe("Authentication (e2e)", () => { const collegEToken = await KeycloakService.shared.getToken( SIMS2_COLLE_USER, - process.env.E2E_TEST_STUDENT_PASSWORD, + process.env.E2E_TEST_PASSWORD, "institution", ); collegEInstitutionUserToken = collegEToken.access_token; @@ -349,7 +349,7 @@ describe("Authentication (e2e)", () => { const collegeELocation = createFakeInstitutionLocation({ institution: collegeE, }); - console.log("collegeELocation:", collegeELocation); + console.log("collegeELocation:", collegeELocation.id); console.log("Before authorizeUserTokenForLocation !#$%%^%*&(*"); await authorizeUserTokenForLocation( db.dataSource, From 2eacff8d8ae87132d9140634b1878180d9264007 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:25:53 -0800 Subject: [PATCH 19/29] checking GHA tests failing --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index adf092455d..96b0dc612c 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -310,7 +310,7 @@ describe("Authentication (e2e)", () => { }, ); - it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { + it.only("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { // Arrange const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, @@ -326,9 +326,6 @@ describe("Authentication (e2e)", () => { InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); // Act/Assert await request(app.getHttpServer()) @@ -340,17 +337,13 @@ describe("Authentication (e2e)", () => { it.only("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange - console.log("Before getAuthRelatedEntities !#@$!%$&"); const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, ); - console.log("Before createFakeInstitutionLocation !#$%%^%*&(*"); const collegeELocation = createFakeInstitutionLocation({ institution: collegeE, }); - console.log("collegeELocation:", collegeELocation.id); - console.log("Before authorizeUserTokenForLocation !#$%%^%*&(*"); await authorizeUserTokenForLocation( db.dataSource, InstitutionTokenTypes.CollegeEReadOnlyUser, @@ -358,15 +351,7 @@ describe("Authentication (e2e)", () => { InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; - // console.log("Before getInstitutionToken !#$%%^%*&(*"); - // const collegEInstitutionUserToken = await getInstitutionToken( - // InstitutionTokenTypes.CollegeEReadOnlyUser, - // ); - console.log( - "collegEInstitutionUserToken:", - collegEInstitutionUserToken.substring(0, 10), - ); - console.log("Before request !#$%%^%*&(*"); + // Act/Assert await request(app.getHttpServer()) .get(endpoint) From 1e62d6cbda0ec8ba411a867ec09739c20dd072ac Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Wed, 8 Jan 2025 16:39:17 -0800 Subject: [PATCH 20/29] reverting changes for testing --- .../backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 4 ++-- sources/tests/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 96b0dc612c..cb19d785d7 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -310,7 +310,7 @@ describe("Authentication (e2e)", () => { }, ); - it.only("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { + it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { // Arrange const { institution: collegeE } = await getAuthRelatedEntities( db.dataSource, @@ -334,7 +334,7 @@ describe("Authentication (e2e)", () => { .expect(HttpStatus.OK); }); - it.only("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { + it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange const { institution: collegeE } = await getAuthRelatedEntities( diff --git a/sources/tests/Makefile b/sources/tests/Makefile index c4534bba7d..82164df95b 100644 --- a/sources/tests/Makefile +++ b/sources/tests/Makefile @@ -21,7 +21,7 @@ backend-unit-tests: e2e-tests-api: @echo "+\n++ Make: Starting containers for API E2E tests \n+" - @docker compose run --name api-e2e-$(BUILD_REF) api npm run test:e2e:api auth.e2e-spec.ts + @docker compose run --name api-e2e-$(BUILD_REF) api npm run test:e2e:api @mkdir -p coverage/api @docker cp api-e2e-$(BUILD_REF):app/apps/api/coverage/clover.xml coverage/api @docker rm api-e2e-$(BUILD_REF) From da93bbbf2bb49a10d3bb26378d2c4e520a1d320b Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 10:09:38 -0800 Subject: [PATCH 21/29] var name and comment --- .../apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 8 ++++---- .../controllers/auth-test/auth-test.controller.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index cb19d785d7..713e2310f8 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -49,7 +49,7 @@ describe("Authentication (e2e)", () => { let db: E2EDataSources; let studentDecodedToken: any; let moduleFixture: TestingModule; - let collegEInstitutionUserToken: string; + let collegEInstitutionReadOnlyUserAccessToken: string; beforeAll(async () => { await KeycloakConfig.load(); @@ -74,7 +74,7 @@ describe("Authentication (e2e)", () => { process.env.E2E_TEST_PASSWORD, "institution", ); - collegEInstitutionUserToken = collegEToken.access_token; + collegEInstitutionReadOnlyUserAccessToken = collegEToken.access_token; moduleFixture = await Test.createTestingModule({ imports: [AppModule, createZeebeModuleMock(), DiscoveryModule], @@ -330,7 +330,7 @@ describe("Authentication (e2e)", () => { // Act/Assert await request(app.getHttpServer()) .get(endpoint) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .auth(collegEInstitutionReadOnlyUserAccessToken, BEARER_AUTH_TYPE) .expect(HttpStatus.OK); }); @@ -355,7 +355,7 @@ describe("Authentication (e2e)", () => { // Act/Assert await request(app.getHttpServer()) .get(endpoint) - .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) + .auth(collegEInstitutionReadOnlyUserAccessToken, BEARER_AUTH_TYPE) .expect(HttpStatus.FORBIDDEN); }); }); diff --git a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts index 962e8ef8b9..f74ed3c1a6 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts @@ -130,7 +130,7 @@ export class AuthTestController { } /** - * Test route which requires a location to be present and the institution user type should be 'user' (non-read-only). + * Test route which requires a location to be present and the institution user type should be non-read-only. */ @HasLocationAccess("locationId", [InstitutionUserTypes.user]) @Get("/institution-location-modifying-route/:locationId") From 16acc5437a4406f2f8e54e984fe3c990bc271c25 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 10:25:52 -0800 Subject: [PATCH 22/29] comment --- .../education-program.institutions.controller.ts | 3 ++- .../apps/api/src/testHelpers/auth/institution-auth-helpers.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts index 5ab2cac912..c94d75fcd1 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts @@ -208,7 +208,8 @@ export class EducationProgramInstitutionsController extends BaseController { /** * Checks if the user is authorized to create or modify programs for the institution. * User should have a user type different from read-only user. - * @param userToken + * @param userToken user token. + * @throws ForbiddenException if the user is not authorized. */ private checkAuthorization( institutionUserAuthorizations: InstitutionUserAuthorizations, diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 2659960c3d..549dee8d77 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -77,7 +77,7 @@ export async function getAuthRelatedEntities( const userRepo = dataSource.getRepository(User); const institution = await institutionRepo.findOneBy({ businessGuid }); const user = await userRepo.findOneBy({ userName }); - return { institution: institution, user }; + return { institution, user }; } /** From cceddf57e910f6be24818a2b01f52abae84fe4de Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 15:41:19 -0800 Subject: [PATCH 23/29] review issues --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 43 +++++-------------- ...plicationOfferingChangeRequest.e2e-spec.ts | 24 ++++------- ...institutions.confirmEnrollment.e2e-spec.ts | 24 ++++------- ...s.denyConfirmationOfEnrollment.e2e-spec.ts | 24 ++++------- ...ions.controller.createOffering.e2e-spec.ts | 23 ++++------ ...ntroller.updateProgramOffering.e2e-spec.ts | 25 ++++------- .../education-program.controller.service.ts | 27 +++++++++++- ...ucation-program.institutions.controller.ts | 40 +++++------------ .../institution-user.controller.service.ts | 2 +- ...troller.saveScholasticStanding.e2e-spec.ts | 24 ++++------- ...astic-standings.institutions.controller.ts | 2 +- .../institution-user-auth.models.ts | 14 ++++++ .../auth/institution-auth-helpers.ts | 31 +++++++++++-- .../auth-test/auth-test.controller.ts | 4 +- .../apps/api/src/types/institutionAuth.ts | 1 + ...e-institutions-and-authentication-users.ts | 5 +-- .../contracts/institution/InstitutionUser.ts | 1 + 17 files changed, 142 insertions(+), 172 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 713e2310f8..924d52bc0a 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -9,7 +9,6 @@ import { } from "@sims/auth/utilities/certificate-utils"; import { createE2EDataSources, - createFakeInstitutionLocation, E2EDataSources, getProviderInstanceForModule, } from "@sims/test-utils"; @@ -21,15 +20,13 @@ import { DataSource } from "typeorm"; import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { - authorizeUserTokenForLocation, BEARER_AUTH_TYPE, - getAuthRelatedEntities, - InstitutionTokenTypes, + getReadOnlyCollegeEAuthorizedLocation, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; import * as dayjs from "dayjs"; -import { InstitutionUserTypes, Student, User } from "@sims/sims-db"; +import { Student, User } from "@sims/sims-db"; import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller"; import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; @@ -312,19 +309,7 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; // Act/Assert @@ -336,27 +321,19 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange - - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; // Act/Assert await request(app.getHttpServer()) .get(endpoint) .auth(collegEInstitutionReadOnlyUserAccessToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); }); diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts index a111a2a345..40692465e9 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts @@ -6,6 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, + getReadOnlyCollegeEAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -20,7 +21,6 @@ import { ApplicationOfferingChangeRequestStatus, ApplicationStatus, InstitutionLocation, - InstitutionUserTypes, OfferingIntensity, } from "@sims/sims-db"; import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validation"; @@ -135,21 +135,8 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl it("Should not be able to submit application offering request with a read-only user.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/location/${collegeELocation.id}/application-offering-change-request`; - // Institution token. const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); @@ -158,7 +145,12 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl await request(app.getHttpServer()) .post(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it("Should throw study overlap error when trying to submit application offering request with an offering, which overlap with students another application.", async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts index 5b881f7ecc..4cd80a2fe2 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts @@ -7,6 +7,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, + getReadOnlyCollegeEAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -25,7 +26,6 @@ import { EducationProgramOffering, Institution, InstitutionLocation, - InstitutionUserTypes, OfferingIntensity, SequenceControl, } from "@sims/sims-db"; @@ -148,21 +148,8 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" it("Should not allow the COE confirmation when the user is read-only.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/999999/confirm`; - // Institution token. const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); @@ -171,7 +158,12 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" await request(app.getHttpServer()) .patch(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it("Should allow the second COE confirmation when the application is on Completed status and all the conditions are fulfilled.", async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts index 2b797c9527..e4822f9884 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts @@ -6,6 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, + getReadOnlyCollegeEAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -23,7 +24,6 @@ import { DisbursementScheduleStatus, Institution, InstitutionLocation, - InstitutionUserTypes, OfferingIntensity, StudentAssessmentStatus, } from "@sims/sims-db"; @@ -86,21 +86,8 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf it("Should not decline the COE when user is read-only.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/9999999/deny`; - // Institution token. const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); @@ -109,7 +96,12 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf await request(app.getHttpServer()) .patch(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it( diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts index 5e620c7343..3d2e6e541c 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts @@ -2,7 +2,6 @@ import { HttpStatus, INestApplication } from "@nestjs/common"; import { Institution, InstitutionLocation, - InstitutionUserTypes, OfferingIntensity, OfferingStatus, OfferingTypes, @@ -23,6 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, + getReadOnlyCollegeEAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -201,19 +201,7 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", ( it("Should not create a new offering when user is read-only.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, @@ -223,7 +211,12 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", ( await request(app.getHttpServer()) .post(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it("Should throw error when education program is expired.", async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts index 34e4612d33..4546926656 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts @@ -2,7 +2,6 @@ import { HttpStatus, INestApplication } from "@nestjs/common"; import { Institution, InstitutionLocation, - InstitutionUserTypes, OfferingIntensity, OfferingStatus, OfferingTypes, @@ -23,6 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, + getReadOnlyCollegeEAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -163,22 +163,8 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer it("Should not update a new offering when requested by a read-only user.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); - + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999/offering/999999`; - const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); @@ -187,7 +173,12 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer await request(app.getHttpServer()) .patch(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it("Should throw error when education program is expired.", async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts index 0868037883..af04bf73aa 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts @@ -1,4 +1,5 @@ import { + ForbiddenException, Injectable, NotFoundException, UnprocessableEntityException, @@ -7,8 +8,13 @@ import { EducationProgramOfferingService, EducationProgramService, FormService, + InstitutionUserAuthorizations, } from "../../services"; -import { EducationProgram, OfferingTypes } from "@sims/sims-db"; +import { + EducationProgram, + InstitutionUserTypes, + OfferingTypes, +} from "@sims/sims-db"; import { PaginatedResultsAPIOutDTO, ProgramsPaginationOptionsAPIInDTO, @@ -306,4 +312,23 @@ export class EducationProgramControllerService { throw error; } } + + /** + * Checks if the user is authorized to create or modify programs for the institution. + * User should have a user type different from read-only user. + * @param userToken user token. + * @throws ForbiddenException if the user is not authorized. + */ + checkInstitutionAuthorization( + institutionUserAuthorizations: InstitutionUserAuthorizations, + ) { + const isAuthorized = institutionUserAuthorizations.hasUserTypeAtAnyLocation( + InstitutionUserTypes.user, + ); + if (!isAuthorized) { + throw new ForbiddenException( + "You are not authorized to create or modify a program.", + ); + } + } } diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts index c94d75fcd1..40dbcbd41a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.institutions.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, DefaultValuePipe, - ForbiddenException, Get, Param, ParseBoolPipe, @@ -31,10 +30,7 @@ import { ApiUnprocessableEntityResponse, } from "@nestjs/swagger"; import BaseController from "../BaseController"; -import { - EducationProgramService, - InstitutionUserAuthorizations, -} from "../../services"; +import { EducationProgramService } from "../../services"; import { PaginatedResultsAPIOutDTO, ProgramsPaginationOptionsAPIInDTO, @@ -42,7 +38,6 @@ import { import { EducationProgramControllerService } from ".."; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; import { OptionItemAPIOutDTO } from "../models/common.dto"; -import { InstitutionUserTypes } from "@sims/sims-db"; @AllowAuthorizedParty(AuthorizedParties.institution) @Controller("education-program") @@ -93,7 +88,9 @@ export class EducationProgramInstitutionsController extends BaseController { @Body() payload: EducationProgramAPIInDTO, @UserToken() userToken: IInstitutionUserToken, ): Promise { - this.checkAuthorization(userToken.authorizations); + this.educationProgramControllerService.checkInstitutionAuthorization( + userToken.authorizations, + ); const newProgram = await this.educationProgramControllerService.saveProgram( payload, userToken.authorizations.institutionId, @@ -125,7 +122,9 @@ export class EducationProgramInstitutionsController extends BaseController { @Body() payload: EducationProgramAPIInDTO, @UserToken() userToken: IInstitutionUserToken, ): Promise { - this.checkAuthorization(userToken.authorizations); + this.educationProgramControllerService.checkInstitutionAuthorization( + userToken.authorizations, + ); await this.educationProgramControllerService.saveProgram( payload, userToken.authorizations.institutionId, @@ -152,7 +151,9 @@ export class EducationProgramInstitutionsController extends BaseController { @Param("programId", ParseIntPipe) programId: number, @UserToken() userToken: IInstitutionUserToken, ): Promise { - this.checkAuthorization(userToken.authorizations); + this.educationProgramControllerService.checkInstitutionAuthorization( + userToken.authorizations, + ); await this.educationProgramControllerService.deactivateProgram( programId, userToken.userId, @@ -204,25 +205,4 @@ export class EducationProgramInstitutionsController extends BaseController { userToken.authorizations.institutionId, ); } - - /** - * Checks if the user is authorized to create or modify programs for the institution. - * User should have a user type different from read-only user. - * @param userToken user token. - * @throws ForbiddenException if the user is not authorized. - */ - private checkAuthorization( - institutionUserAuthorizations: InstitutionUserAuthorizations, - ) { - const isAuthorized = institutionUserAuthorizations.authorizations.some( - (auth) => - auth.userType === InstitutionUserTypes.admin || - auth.userType === InstitutionUserTypes.user, - ); - if (!isAuthorized) { - throw new ForbiddenException( - "You are not authorized to create or modify a program.", - ); - } - } } diff --git a/sources/packages/backend/apps/api/src/route-controllers/institution-user/institution-user.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/institution-user/institution-user.controller.service.ts index 13023bee5f..f15fcfa577 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/institution-user/institution-user.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/institution-user/institution-user.controller.service.ts @@ -32,7 +32,7 @@ import { INSTITUTION_USER_ALREADY_EXISTS, LEGAL_SIGNING_AUTHORITY_EXIST, } from "../../constants"; -import { InstitutionUserTypes } from "../../auth/user-types.enum"; +import { InstitutionUserTypes } from "../../auth"; /** * Service/Provider for Institutions controller to wrap the common methods. diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts index 244cd61fd9..9f3efe9f5d 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts @@ -15,13 +15,13 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, + getReadOnlyCollegeEAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import { ApplicationStatus, AssessmentTriggerType, InstitutionLocation, - InstitutionUserTypes, NotificationMessageType, OfferingIntensity, StudentScholasticStandingChangeType, @@ -263,21 +263,8 @@ describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticSt it("Should not create a new scholastic standing when the user is read-only.", async () => { // Arrange - const { institution: collegeE } = await getAuthRelatedEntities( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); - await authorizeUserTokenForLocation( - db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, - InstitutionUserTypes.readOnlyUser, - ); + const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); const endpoint = `/institutions/scholastic-standing/location/${collegeELocation.id}/application/99999`; - // Institution token. const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, ); @@ -286,7 +273,12 @@ describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticSt await request(app.getHttpServer()) .post(endpoint) .auth(collegEInstitutionUserToken, BEARER_AUTH_TYPE) - .expect(HttpStatus.FORBIDDEN); + .expect(HttpStatus.FORBIDDEN) + .expect({ + statusCode: HttpStatus.FORBIDDEN, + message: "Forbidden resource", + error: "Forbidden", + }); }); it("Should create a new scholastic standing 'School transfer' for a part-time application when the institution user requests.", async () => { diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts index 17d4cec917..b7539f2ab2 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/student-scholastic-standings.institutions.controller.ts @@ -63,7 +63,7 @@ import { uploadLimits, } from "../../utilities"; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; -import { InstitutionUserTypes } from "@sims/sims-db"; +import { InstitutionUserTypes } from "../../auth"; /** * Scholastic standing controller for institutions Client. diff --git a/sources/packages/backend/apps/api/src/services/institution-user-auth/institution-user-auth.models.ts b/sources/packages/backend/apps/api/src/services/institution-user-auth/institution-user-auth.models.ts index 03f994129c..340f6f7d64 100644 --- a/sources/packages/backend/apps/api/src/services/institution-user-auth/institution-user-auth.models.ts +++ b/sources/packages/backend/apps/api/src/services/institution-user-auth/institution-user-auth.models.ts @@ -111,6 +111,20 @@ export class InstitutionUserAuthorizations { .filter((auth) => auth.locationId) .map((auth) => auth.locationId); } + + /** + * Determines whether the user has a user type associated with any location. + * @param userType User type to check the permission. + * @returns true if the user type is present at any location the user is associated with. + */ + hasUserTypeAtAnyLocation(institutionUserType: InstitutionUserTypes): boolean { + if (this.isAdmin()) { + return true; + } + return this.authorizations.some( + (auth) => auth.userType === institutionUserType, + ); + } } export interface Authorizations { diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 549dee8d77..50662e1f71 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -8,7 +8,11 @@ import { InstitutionUserTypes, User, } from "@sims/sims-db"; -import { createFakeInstitutionUser } from "@sims/test-utils"; +import { + createFakeInstitutionLocation, + createFakeInstitutionUser, + E2EDataSources, +} from "@sims/test-utils"; import { COLLEGE_C_BUSINESS_GUID, COLLEGE_D_BUSINESS_GUID, @@ -134,7 +138,9 @@ export async function authorizeUserTokenForLocation( dataSource: DataSource, userTokenType: InstitutionTokenTypes, location: InstitutionLocation, - institutionUserType?: InstitutionUserTypes, + options?: { + institutionUserType?: InstitutionUserTypes; + }, ) { const { institution, user } = await getAuthRelatedEntities( dataSource, @@ -149,8 +155,27 @@ export async function authorizeUserTokenForLocation( institution.id, user.id, location.id, - institutionUserType ?? InstitutionUserTypes.user, + options?.institutionUserType ?? InstitutionUserTypes.user, + ); +} + +export async function getReadOnlyCollegeEAuthorizedLocation( + db: E2EDataSources, +) { + const { institution: collegeE } = await getAuthRelatedEntities( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); + const collegeELocation = createFakeInstitutionLocation({ + institution: collegeE, + }); + await authorizeUserTokenForLocation( + db.dataSource, + InstitutionTokenTypes.CollegeEReadOnlyUser, + collegeELocation, + { institutionUserType: InstitutionUserTypes.readOnlyUser }, ); + return collegeELocation; } export const INSTITUTION_BC_PUBLIC_ERROR_MESSAGE = diff --git a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts index f74ed3c1a6..dd1367b235 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/controllers/auth-test/auth-test.controller.ts @@ -9,9 +9,7 @@ import { RequiresUserAccount, HasLocationAccess, } from "../../../auth/decorators"; -import { Role } from "../../../auth/roles.enum"; -import { UserGroups } from "../../../auth/user-groups.enum"; -import { InstitutionUserTypes } from "@sims/sims-db"; +import { Role, UserGroups, InstitutionUserTypes } from "../../../auth"; /** * Controller dedicated to test the functionalities around the authentication layer. diff --git a/sources/packages/backend/apps/api/src/types/institutionAuth.ts b/sources/packages/backend/apps/api/src/types/institutionAuth.ts index 54ddc80173..ca5681fa7e 100644 --- a/sources/packages/backend/apps/api/src/types/institutionAuth.ts +++ b/sources/packages/backend/apps/api/src/types/institutionAuth.ts @@ -1,6 +1,7 @@ export enum InstitutionUserType { admin = "admin", user = "user", + readOnlyUser = "read-only-user", } export enum InstitutionUserRole { diff --git a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts index 906ff090ce..997a1b5135 100644 --- a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts +++ b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts @@ -99,10 +99,7 @@ export class CreateInstitutionsAndAuthenticationUsers { // Check if a default location need to be created. // Only read-only and regular users must be associated with a location. let location: InstitutionLocation; - if ( - user.userType === InstitutionUserTypes.user || - user.userType === InstitutionUserTypes.readOnlyUser - ) { + if (user.userType !== InstitutionUserTypes.admin) { // Crate a default location. // Create a default location to have it associated with the regular user. const fakeInstitutionDefaultLocation = createFakeInstitutionLocation({ diff --git a/sources/packages/web/src/types/contracts/institution/InstitutionUser.ts b/sources/packages/web/src/types/contracts/institution/InstitutionUser.ts index de7880c616..b9bc054dcb 100644 --- a/sources/packages/web/src/types/contracts/institution/InstitutionUser.ts +++ b/sources/packages/web/src/types/contracts/institution/InstitutionUser.ts @@ -3,6 +3,7 @@ import { InstitutionLocationsDetails } from "./InstitutionLocation"; export enum InstitutionUserTypes { admin = "admin", user = "user", + readOnlyUser = "read-only-user", } export enum InstitutionUserRoles { From ec915642c907fdab30ffd2dd2f7fba1c570a8157 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 15:52:28 -0800 Subject: [PATCH 24/29] review issues --- .../apps/api/src/testHelpers/auth/institution-auth-helpers.ts | 1 + .../create-institutions-and-authentication-users.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 50662e1f71..c99f2d0bcc 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -133,6 +133,7 @@ async function authorizeUserForLocation( * @param userTokenType user type that need to be associated. This will be the * same userTokenType used to authenticate to the API. * @param location location to have the access granted for the user. + * @param options optional parameters. */ export async function authorizeUserTokenForLocation( dataSource: DataSource, diff --git a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts index 997a1b5135..cd96dde860 100644 --- a/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts +++ b/sources/packages/backend/apps/test-db-seeding/src/db-seeding/institution/basic-seeding/create-institutions-and-authentication-users.ts @@ -97,7 +97,7 @@ export class CreateInstitutionsAndAuthenticationUsers { user.userRole, ); // Check if a default location need to be created. - // Only read-only and regular users must be associated with a location. + // Only non-admin users must be associated with a location. let location: InstitutionLocation; if (user.userType !== InstitutionUserTypes.admin) { // Crate a default location. From f1dbdb9b63eca58d1da70471335ea566a48b9108 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 15:54:30 -0800 Subject: [PATCH 25/29] review issues --- .../api/src/testHelpers/auth/institution-auth-helpers.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index c99f2d0bcc..3396008041 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -160,6 +160,13 @@ export async function authorizeUserTokenForLocation( ); } +/** + * Create a location for College E with read only user access. + * This is useful for tests that need to assert that the API endpoints + * are properly restricted for read only users. + * @param db E2E testing data sources. + * @returns location with read only access for College E. + */ export async function getReadOnlyCollegeEAuthorizedLocation( db: E2EDataSources, ) { From 12a540ec98191279f178ddaf8c9c3adcb9c606ca Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Thu, 9 Jan 2025 16:04:04 -0800 Subject: [PATCH 26/29] review issues --- .../api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 13 +++++++++--- ...plicationOfferingChangeRequest.e2e-spec.ts | 7 +++++-- ...institutions.confirmEnrollment.e2e-spec.ts | 7 +++++-- ...s.denyConfirmationOfEnrollment.e2e-spec.ts | 7 +++++-- ...ions.controller.createOffering.e2e-spec.ts | 7 +++++-- ...ntroller.updateProgramOffering.e2e-spec.ts | 7 +++++-- ...troller.saveScholasticStanding.e2e-spec.ts | 7 +++++-- .../auth/institution-auth-helpers.ts | 21 +++++++++---------- 8 files changed, 50 insertions(+), 26 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 924d52bc0a..429f340249 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -21,7 +21,8 @@ import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { BEARER_AUTH_TYPE, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, + InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, } from "../../../testHelpers"; @@ -309,7 +310,10 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; // Act/Assert @@ -321,7 +325,10 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; // Act/Assert diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts index 40692465e9..f9f41c94db 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts @@ -6,7 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -135,7 +135,10 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl it("Should not be able to submit application offering request with a read-only user.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/location/${collegeELocation.id}/application-offering-change-request`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts index 4cd80a2fe2..003b6e7d92 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts @@ -7,7 +7,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -148,7 +148,10 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" it("Should not allow the COE confirmation when the user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/999999/confirm`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts index e4822f9884..d0206c0962 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts @@ -6,7 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -86,7 +86,10 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf it("Should not decline the COE when user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/9999999/deny`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts index 3d2e6e541c..b3e0354b9e 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts @@ -22,7 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -201,7 +201,10 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", ( it("Should not create a new offering when user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts index 4546926656..6ed4812876 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts @@ -22,7 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -163,7 +163,10 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer it("Should not update a new offering when requested by a read-only user.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999/offering/999999`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts index 9f3efe9f5d..61ce2b7d04 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts @@ -15,7 +15,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyCollegeEAuthorizedLocation, + getReadOnlyAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import { @@ -263,7 +263,10 @@ describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticSt it("Should not create a new scholastic standing when the user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyCollegeEAuthorizedLocation(db); + const collegeELocation = await getReadOnlyAuthorizedLocation( + db, + InstitutionTokenTypes.CollegeEReadOnlyUser, + ); const endpoint = `/institutions/scholastic-standing/location/${collegeELocation.id}/application/99999`; const collegEInstitutionUserToken = await getInstitutionToken( InstitutionTokenTypes.CollegeEReadOnlyUser, diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 3396008041..f552be3cff 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -161,29 +161,28 @@ export async function authorizeUserTokenForLocation( } /** - * Create a location for College E with read only user access. + * Create a location with read only user access. * This is useful for tests that need to assert that the API endpoints * are properly restricted for read only users. * @param db E2E testing data sources. - * @returns location with read only access for College E. + * @returns location the user will have read only access. */ -export async function getReadOnlyCollegeEAuthorizedLocation( +export async function getReadOnlyAuthorizedLocation( db: E2EDataSources, + institutionTokenType: InstitutionTokenTypes, ) { - const { institution: collegeE } = await getAuthRelatedEntities( + const { institution } = await getAuthRelatedEntities( db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, + institutionTokenType, ); - const collegeELocation = createFakeInstitutionLocation({ - institution: collegeE, - }); + const location = createFakeInstitutionLocation({ institution }); await authorizeUserTokenForLocation( db.dataSource, - InstitutionTokenTypes.CollegeEReadOnlyUser, - collegeELocation, + institutionTokenType, + location, { institutionUserType: InstitutionUserTypes.readOnlyUser }, ); - return collegeELocation; + return location; } export const INSTITUTION_BC_PUBLIC_ERROR_MESSAGE = From 7d1bf1c4fbceae3479a8c46a16323e4a8eee0def Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Fri, 10 Jan 2025 09:34:44 -0800 Subject: [PATCH 27/29] review issues --- .../apps/api/src/testHelpers/auth/institution-auth-helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index f552be3cff..829cd96132 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -165,6 +165,7 @@ export async function authorizeUserTokenForLocation( * This is useful for tests that need to assert that the API endpoints * are properly restricted for read only users. * @param db E2E testing data sources. + * @param institutionTokenType user type that will have read only access. * @returns location the user will have read only access. */ export async function getReadOnlyAuthorizedLocation( From 54d6dab7037bc78e3ee99c3835185435eb21551b Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Mon, 13 Jan 2025 09:53:49 -0800 Subject: [PATCH 28/29] review issues --- .../apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts | 9 ++++++--- ...createApplicationOfferingChangeRequest.e2e-spec.ts | 6 ++++-- ...offering-change-request.institutions.controller.ts | 6 ++---- ...ollment.institutions.confirmEnrollment.e2e-spec.ts | 6 ++++-- ...titutions.denyConfirmationOfEnrollment.e2e-spec.ts | 6 ++++-- ...firmation-of-enrollment.institutions.controller.ts | 3 ++- ...institutions.controller.createOffering.e2e-spec.ts | 6 ++++-- ...tions.controller.updateProgramOffering.e2e-spec.ts | 6 ++++-- ...cation-program-offering.institutions.controller.ts | 7 ++----- .../education-program.controller.service.ts | 7 ++----- .../program-info-request.institutions.controller.ts | 2 +- ...ions.controller.saveScholasticStanding.e2e-spec.ts | 6 ++++-- .../src/testHelpers/auth/institution-auth-helpers.ts | 11 +++++++---- 13 files changed, 46 insertions(+), 35 deletions(-) diff --git a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts index 429f340249..ce4d60cc34 100644 --- a/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/auth/_tests_/e2e/auth.e2e-spec.ts @@ -21,7 +21,7 @@ import { JwtService } from "@nestjs/jwt"; import { INVALID_BETA_USER, MISSING_USER_ACCOUNT } from "../../../constants"; import { BEARER_AUTH_TYPE, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, InstitutionTokenTypes, mockUserLoginInfo, resetMockUserLoginInfo, @@ -30,6 +30,7 @@ import * as dayjs from "dayjs"; import { Student, User } from "@sims/sims-db"; import { AuthTestController } from "../../../testHelpers/controllers/auth-test/auth-test.controller"; import { SIMS2_COLLE_USER } from "@sims/test-utils/constants"; +import { InstitutionUserTypes } from "../../user-types.enum"; describe("Authentication (e2e)", () => { // Nest application to be shared for all e2e tests @@ -310,9 +311,10 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus OK(200) when a read-only institution user tries to access a read-only route to their institution.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-reading-route/${collegeELocation.id}`; @@ -325,9 +327,10 @@ describe("Authentication (e2e)", () => { it("Should return a HttpStatus FORBIDDEN(403) when a read-only institution user tries to access a non-reading-only route to their institution.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/auth-test/institution-location-modifying-route/${collegeELocation.id}`; diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts index f9f41c94db..77c84ec04e 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/_tests_/e2e/application-offering-change-request.institutions.controller.createApplicationOfferingChangeRequest.e2e-spec.ts @@ -6,7 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -27,6 +27,7 @@ import { createFakeSINValidation } from "@sims/test-utils/factories/sin-validati import { addDays, getISODateOnlyString } from "@sims/utilities"; import { STUDY_DATE_OVERLAP_ERROR } from "../../../../utilities"; import { OFFERING_INTENSITY_MISMATCH } from "../../../../constants"; +import { InstitutionUserTypes } from "../../../../auth"; describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createApplicationOfferingChangeRequest", () => { let app: INestApplication; @@ -135,9 +136,10 @@ describe("ApplicationOfferingChangeRequestInstitutionsController(e2e)-createAppl it("Should not be able to submit application offering request with a read-only user.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/location/${collegeELocation.id}/application-offering-change-request`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts index a9992cf4a3..1d6d975d50 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts @@ -43,10 +43,7 @@ import { ApplicationOfferingChangeRequestService, ApplicationService, } from "../../services"; -import { - ApplicationOfferingChangeRequestStatus, - InstitutionUserTypes, -} from "@sims/sims-db"; +import { ApplicationOfferingChangeRequestStatus } from "@sims/sims-db"; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; import { IInstitutionUserToken } from "../../auth"; import { CustomNamedError } from "@sims/utilities"; @@ -59,6 +56,7 @@ import { } from "../../constants"; import { InjectLogger, LoggerService } from "@sims/utilities/logger"; import { ApplicationOfferingChangeRequestControllerService } from "./application-offering-change-request.controller.service"; +import { InstitutionUserTypes } from "../../auth"; /** * Application offering change request controller for institutions client. diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts index 003b6e7d92..d5254c806a 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.confirmEnrollment.e2e-spec.ts @@ -7,7 +7,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -31,6 +31,7 @@ import { } from "@sims/sims-db"; import { MONEY_VALUE_FOR_UNKNOWN_MAX_VALUE } from "../../../../utilities"; import { COE_WINDOW, addDays, getISODateOnlyString } from "@sims/utilities"; +import { InstitutionUserTypes } from "../../../../auth"; describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment", () => { let app: INestApplication; @@ -148,9 +149,10 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-confirmEnrollment" it("Should not allow the COE confirmation when the user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/999999/confirm`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts index d0206c0962..bacee342a7 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/_tests_/e2e/confirmation-of-enrollment.institutions.denyConfirmationOfEnrollment.e2e-spec.ts @@ -6,7 +6,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, InstitutionTokenTypes, } from "../../../../testHelpers"; import { @@ -28,6 +28,7 @@ import { StudentAssessmentStatus, } from "@sims/sims-db"; import { COE_WINDOW, addDays, getISODateOnlyString } from "@sims/utilities"; +import { InstitutionUserTypes } from "../../../../auth"; describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOfEnrollment", () => { let app: INestApplication; @@ -86,9 +87,10 @@ describe("ConfirmationOfEnrollmentInstitutionsController(e2e)-denyConfirmationOf it("Should not decline the COE when user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/location/${collegeELocation.id}/confirmation-of-enrollment/disbursement-schedule/9999999/deny`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts index 69ccc77435..434ef808b0 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/confirmation-of-enrollment/confirmation-of-enrollment.institutions.controller.ts @@ -21,7 +21,7 @@ import { COEDeniedReasonService, DisbursementScheduleService, } from "../../services"; -import { DisbursementSchedule, InstitutionUserTypes } from "@sims/sims-db"; +import { DisbursementSchedule } from "@sims/sims-db"; import { getUserFullName } from "../../utilities/auth-utils"; import { getCOEDeniedReason, @@ -62,6 +62,7 @@ import { ENROLMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE, ENROLMENT_NOT_FOUND, } from "@sims/services/constants"; +import { InstitutionUserTypes } from "../../auth"; @AllowAuthorizedParty(AuthorizedParties.institution) @Controller("location") diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts index b3e0354b9e..ef8260a955 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.createOffering.e2e-spec.ts @@ -22,7 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -37,6 +37,7 @@ import { } from "../../models/education-program-offering.dto"; import { WILComponentOptions } from "../../../../services"; import { getISODateOnlyString } from "@sims/utilities"; +import { InstitutionUserTypes } from "../../../../auth"; describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", () => { let app: INestApplication; @@ -201,9 +202,10 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-createOffering", ( it("Should not create a new offering when user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts index 6ed4812876..1321b7b175 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/_tests_/e2e/education-program-offering.institutions.controller.updateProgramOffering.e2e-spec.ts @@ -22,7 +22,7 @@ import { getAuthRelatedEntities, createFakeEducationProgram, createFakeEducationProgramOffering, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import * as faker from "faker"; @@ -31,6 +31,7 @@ import { MONEY_VALUE_FOR_UNKNOWN_MAX_VALUE, } from "../../../../utilities"; import { getISODateOnlyString } from "@sims/utilities"; +import { InstitutionUserTypes } from "../../../../auth"; describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffering", () => { let app: INestApplication; @@ -163,9 +164,10 @@ describe("EducationProgramOfferingInstitutionsController(e2e)-updateProgramOffer it("Should not update a new offering when requested by a read-only user.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/education-program-offering/location/${collegeELocation.id}/education-program/999999/offering/999999`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts index 8a0e389098..5ce90b3844 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program-offering/education-program-offering.institutions.controller.ts @@ -32,11 +32,7 @@ import { UserToken, } from "../../auth/decorators"; import { IInstitutionUserToken } from "../../auth/userToken.interface"; -import { - InstitutionUserTypes, - OfferingIntensity, - OfferingTypes, -} from "@sims/sims-db"; +import { OfferingIntensity, OfferingTypes } from "@sims/sims-db"; import { CreateFromValidatedOfferingError, EducationProgramOfferingService, @@ -80,6 +76,7 @@ import { } from "../../constants"; import { OfferingCSVModel } from "../../services/education-program-offering/education-program-offering-import-csv.models"; import { Request } from "express"; +import { InstitutionUserTypes } from "../../auth"; @AllowAuthorizedParty(AuthorizedParties.institution) @Controller("education-program-offering") diff --git a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts index af04bf73aa..cb7d0f964c 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/education-program/education-program.controller.service.ts @@ -10,11 +10,7 @@ import { FormService, InstitutionUserAuthorizations, } from "../../services"; -import { - EducationProgram, - InstitutionUserTypes, - OfferingTypes, -} from "@sims/sims-db"; +import { EducationProgram, OfferingTypes } from "@sims/sims-db"; import { PaginatedResultsAPIOutDTO, ProgramsPaginationOptionsAPIInDTO, @@ -42,6 +38,7 @@ import { SaveEducationProgram, } from "../../services/education-program/education-program.service.models"; import { InstitutionService } from "../../services/institution/institution.service"; +import { InstitutionUserTypes } from "../../auth"; @Injectable() export class EducationProgramControllerService { diff --git a/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts index 0e26b89d9d..74330fac64 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/program-info-request/program-info-request.institutions.controller.ts @@ -34,7 +34,6 @@ import { CustomNamedError, getISODateOnlyString } from "@sims/utilities"; import { Application, AssessmentTriggerType, - InstitutionUserTypes, ProgramInfoStatus, } from "@sims/sims-db"; import { @@ -55,6 +54,7 @@ import { import BaseController from "../BaseController"; import { ApiProcessError, ClientTypeBaseRoute } from "../../types"; import { WorkflowClientService } from "@sims/services"; +import { InstitutionUserTypes } from "../../auth"; @AllowAuthorizedParty(AuthorizedParties.institution) @Controller("location") diff --git a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts index 61ce2b7d04..f7f84f6d07 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/student-scholastic-standings/_tests_/e2e/student-scholastic-standings.institutions.controller.saveScholasticStanding.e2e-spec.ts @@ -15,7 +15,7 @@ import { createTestingAppModule, getAuthRelatedEntities, getInstitutionToken, - getReadOnlyAuthorizedLocation, + getAuthorizedLocation, } from "../../../../testHelpers"; import * as request from "supertest"; import { @@ -36,6 +36,7 @@ import { addToDateOnlyString, getISODateOnlyString } from "@sims/utilities"; import { AppInstitutionsModule } from "../../../../app.institutions.module"; import { TestingModule } from "@nestjs/testing"; import { APPLICATION_CHANGE_NOT_ELIGIBLE } from "../../../../constants"; +import { InstitutionUserTypes } from "../../../../auth"; describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticStanding.", () => { let app: INestApplication; @@ -263,9 +264,10 @@ describe("StudentScholasticStandingsInstitutionsController(e2e)-saveScholasticSt it("Should not create a new scholastic standing when the user is read-only.", async () => { // Arrange - const collegeELocation = await getReadOnlyAuthorizedLocation( + const collegeELocation = await getAuthorizedLocation( db, InstitutionTokenTypes.CollegeEReadOnlyUser, + InstitutionUserTypes.readOnlyUser, ); const endpoint = `/institutions/scholastic-standing/location/${collegeELocation.id}/application/99999`; const collegEInstitutionUserToken = await getInstitutionToken( diff --git a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts index 829cd96132..ff4a7b178a 100644 --- a/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts +++ b/sources/packages/backend/apps/api/src/testHelpers/auth/institution-auth-helpers.ts @@ -134,6 +134,7 @@ async function authorizeUserForLocation( * same userTokenType used to authenticate to the API. * @param location location to have the access granted for the user. * @param options optional parameters. + * - `institutionUserType` type of the institution user. */ export async function authorizeUserTokenForLocation( dataSource: DataSource, @@ -161,16 +162,18 @@ export async function authorizeUserTokenForLocation( } /** - * Create a location with read only user access. + * Create a location with user access. * This is useful for tests that need to assert that the API endpoints - * are properly restricted for read only users. + * are properly restricted to their institution user type. * @param db E2E testing data sources. * @param institutionTokenType user type that will have read only access. + * @param institutionUserType type of the institution user. * @returns location the user will have read only access. */ -export async function getReadOnlyAuthorizedLocation( +export async function getAuthorizedLocation( db: E2EDataSources, institutionTokenType: InstitutionTokenTypes, + institutionUserType: InstitutionUserTypes, ) { const { institution } = await getAuthRelatedEntities( db.dataSource, @@ -181,7 +184,7 @@ export async function getReadOnlyAuthorizedLocation( db.dataSource, institutionTokenType, location, - { institutionUserType: InstitutionUserTypes.readOnlyUser }, + { institutionUserType }, ); return location; } From 1812ed21e4c62e26423333ee09c87113f96edfe8 Mon Sep 17 00:00:00 2001 From: Andre Pestana Date: Mon, 13 Jan 2025 09:57:38 -0800 Subject: [PATCH 29/29] fixed import --- ...lication-offering-change-request.institutions.controller.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts index 1d6d975d50..a4c561bd8c 100644 --- a/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts +++ b/sources/packages/backend/apps/api/src/route-controllers/application-offering-change-request/application-offering-change-request.institutions.controller.ts @@ -45,7 +45,7 @@ import { } from "../../services"; import { ApplicationOfferingChangeRequestStatus } from "@sims/sims-db"; import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto"; -import { IInstitutionUserToken } from "../../auth"; +import { IInstitutionUserToken, InstitutionUserTypes } from "../../auth"; import { CustomNamedError } from "@sims/utilities"; import { EDUCATION_PROGRAM_IS_EXPIRED, @@ -56,7 +56,6 @@ import { } from "../../constants"; import { InjectLogger, LoggerService } from "@sims/utilities/logger"; import { ApplicationOfferingChangeRequestControllerService } from "./application-offering-change-request.controller.service"; -import { InstitutionUserTypes } from "../../auth"; /** * Application offering change request controller for institutions client.