diff --git a/src/casl/authop.enum.ts b/src/casl/action.enum.ts similarity index 96% rename from src/casl/authop.enum.ts rename to src/casl/action.enum.ts index 832cf77a6..6eb84b3a6 100644 --- a/src/casl/authop.enum.ts +++ b/src/casl/action.enum.ts @@ -1,5 +1,4 @@ -/** Strings representing authorization groups for the various CRUD operations */ -export enum AuthOp { +export enum Action { Manage = "manage", Create = "create", Read = "read", @@ -12,7 +11,7 @@ export enum AuthOp { ListAll = "listall", // --------------- // Datasets - // endpoint authorization + // endpoint authorization actions DatasetCreate = "dataset_create", DatasetRead = "dataset_read", DatasetUpdate = "dataset_update", @@ -30,7 +29,7 @@ export enum AuthOp { DatasetDatablockUpdate = "dataset_datablock_update", DatasetDatablockDelete = "dataset_datablock_delete", DatasetLogbookRead = "dataset_logbook_read", - // data instance authorization + // data instance actions DatasetCreateOwnerNoPid = "dataset_create_owner_no_pid", DatasetCreateOwnerWithPid = "dataset_create_owner_with_pid", DatasetCreateAny = "dataset_create_any", @@ -77,14 +76,15 @@ export enum AuthOp { DatasetDatablockDeleteAny = "dataset_datablock_delete_any", DatasetLogbookReadOwner = "dataset_logbook_read_owner", DatasetLogbookReadAny = "dataset_logbook_read_any", + // // ------------- // Origdatablock - // endpoint authorization + // endpoint authorization actions OrigdatablockCreate = "origdatablock_create", OrigdatablockRead = "origdatablock_read", OrigdatablockUpdate = "origdatablock_update", OrigdatablockDelete = "origdatablock_delete", - // data instance authorization + // individual actions OrigdatablockCreateOwner = "origdatablock_create_owner", OrigdatablockCreateAny = "origdatablock_create_any", OrigdatablockReadManyPublic = "origdatablock_read_many_public", @@ -98,9 +98,9 @@ export enum AuthOp { OrigdatablockUpdateAny = "origdatablock_update_any", OrigdatablockDeleteOwner = "origdatablock_delete_owner", OrigdatablockDeleteAny = "origdatablock_delete_any", - // ------------- + // Proposals - // endpoint authorization + // endpoint authorization actions ProposalsCreate = "proposals_create", ProposalsRead = "proposals_read", ProposalsUpdate = "proposals_update", @@ -110,7 +110,8 @@ export enum AuthOp { ProposalsAttachmentUpdate = "proposals_attachment_update", ProposalsAttachmentDelete = "proposals_attachment_delete", ProposalsDatasetRead = "proposals_dataset_read", - // data instance authorization + + // individual actions ProposalsCreateOwner = "proposals_create_owner", ProposalsCreateAny = "proposals_create_any", ProposalsReadManyPublic = "proposals_read_many_public", @@ -177,6 +178,7 @@ export enum AuthOp { // -------------- // Jobs + // -------------- // endpoint authorization JobCreate = "jobs_create", JobRead = "jobs_read", @@ -191,9 +193,10 @@ export enum AuthOp { JobStatusUpdateConfiguration = "job_status_update_configuration", JobStatusUpdateOwner = "job_status_update_owner", JobStatusUpdateAny = "job_status_update_any", - JobDeleteAny = "job_delete_any", + //JobDeleteAny = "job_delete_any", + // ------------- - // Users + // Users actions UserReadOwn = "user_read_own", UserReadAny = "user_read_any", UserCreateOwn = "user_create_own", @@ -203,8 +206,7 @@ export enum AuthOp { UserDeleteOwn = "user_delete_own", UserDeleteAny = "user_delete_any", UserCreateJwt = "user_create_jwt", - // ------------- - // Instrument + // Instrument actions InstrumentRead = "instrument_read", InstrumentUpdate = "instrument_update", InstrumentCreate = "instrument_create", diff --git a/src/casl/casl-ability.factory.ts b/src/casl/casl-ability.factory.ts index c0aa2de27..d3dd8a4f9 100644 --- a/src/casl/casl-ability.factory.ts +++ b/src/casl/casl-ability.factory.ts @@ -1,11 +1,12 @@ import { - Ability, AbilityBuilder, - AbilityClass, ExtractSubjectType, InferSubjects, + MongoAbility, + MongoQuery, + createMongoAbility, } from "@casl/ability"; -import { Injectable } from "@nestjs/common"; +import { Injectable, InternalServerErrorException } from "@nestjs/common"; import { Attachment } from "src/attachments/schemas/attachment.schema"; import { JWTUser } from "src/auth/interfaces/jwt-user.interface"; // import { Role } from "src/auth/role.enum"; @@ -23,13 +24,15 @@ import { SampleClass } from "src/samples/schemas/sample.schema"; import { UserIdentity } from "src/users/schemas/user-identity.schema"; import { UserSettings } from "src/users/schemas/user-settings.schema"; import { User } from "src/users/schemas/user.schema"; -import { AuthOp } from "./authop.enum"; +import { Action } from "./action.enum"; import configuration from "src/config/configuration"; import { CreateJobAuth, StatusUpdateJobAuth, } from "src/jobs/types/jobs-auth.enum"; +import { JobConfig } from "src/jobs/config/jobconfig"; + type Subjects = | string | InferSubjects< @@ -50,162 +53,793 @@ type Subjects = | typeof ElasticSearchActions > | "all"; +type PossibleAbilities = [Action, Subjects]; +type Conditions = MongoQuery; + +export type AppAbility = MongoAbility; + +@Injectable() +export class CaslAbilityFactory { + private endpointAccessors: { + [endpoint: string]: (user: JWTUser) => AppAbility; + } = { + datasets: this.datasetEndpointAccess, + "elastic-search": this.elasticSearchEndpointAccess, + jobs: this.jobsEndpointAccess, + instruments: this.instrumentEndpointAccess, + logbooks: this.logbookEndpointAccess, + origdatablocks: this.origDatablockEndpointAccess, + policies: this.policyEndpointAccess, + proposals: this.proposalsEndpointAccess, + publisheddata: this.publishedDataEndpointAccess, + samples: this.samplesEndpointAccess, + users: this.userEndpointAccess, + }; + + endpointAccess(endpoint: string, user: JWTUser) { + const accessFunction = this.endpointAccessors[endpoint]; + if (!accessFunction) { + throw new InternalServerErrorException( + `No endpoint access policies defined for subject: ${endpoint}`, + ); + } + return accessFunction.call(this, user); + } + + datasetEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + /** + /* unauthenticated users + **/ + + cannot(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + cannot(Action.DatasetUpdate, DatasetClass); + // - + cannot(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + cannot(Action.DatasetAttachmentUpdate, DatasetClass); + cannot(Action.DatasetAttachmentDelete, DatasetClass); + // - + cannot(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + cannot(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + cannot(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + cannot(Action.DatasetDatablockUpdate, DatasetClass); + // - + cannot(Action.DatasetLogbookRead, DatasetClass); + } else { + if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ + + can(Action.DatasetDelete, DatasetClass); + // - + can(Action.DatasetOrigdatablockDelete, DatasetClass); + // - + can(Action.DatasetDatablockDelete, DatasetClass); + } else { + /* + / user that does not belong to any of the group listed in DELETE_GROUPS + */ + + cannot(Action.DatasetDelete, DatasetClass); + // - + cannot(Action.DatasetOrigdatablockDelete, DatasetClass); + // - + cannot(Action.DatasetDatablockDelete, DatasetClass); + } + + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ + + can(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + can(Action.DatasetUpdate, DatasetClass); + // - + can(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + can(Action.DatasetAttachmentUpdate, DatasetClass); + can(Action.DatasetAttachmentDelete, DatasetClass); + // - + can(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + can(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + can(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + can(Action.DatasetDatablockUpdate, DatasetClass); + // - + can(Action.DatasetLogbookRead, DatasetClass); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetPrivilegedGroups.includes(g), + ) + ) { + /** + /* users belonging to CREATE_DATASET_PRIVILEGED_GROUPS + **/ + + can(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + can(Action.DatasetUpdate, DatasetClass); + // - + can(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + can(Action.DatasetAttachmentUpdate, DatasetClass); + can(Action.DatasetAttachmentDelete, DatasetClass); + // - + can(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + can(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + can(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + can(Action.DatasetDatablockUpdate, DatasetClass); + // - + can(Action.DatasetLogbookRead, DatasetClass); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetWithPidGroups.includes(g), + ) || + configuration().createDatasetWithPidGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_WITH_PID_GROUPS + **/ + + can(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + can(Action.DatasetUpdate, DatasetClass); + // - + can(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + can(Action.DatasetAttachmentUpdate, DatasetClass); + can(Action.DatasetAttachmentDelete, DatasetClass); + // - + can(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + can(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + can(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + can(Action.DatasetDatablockUpdate, DatasetClass); + // - + can(Action.DatasetLogbookRead, DatasetClass); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetGroups.includes(g), + ) || + configuration().createDatasetGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_GROUPS + **/ + + can(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + can(Action.DatasetUpdate, DatasetClass); + // - + can(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + can(Action.DatasetAttachmentUpdate, DatasetClass); + can(Action.DatasetAttachmentDelete, DatasetClass); + // - + can(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + can(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + can(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + can(Action.DatasetDatablockUpdate, DatasetClass); + // - + can(Action.DatasetLogbookRead, DatasetClass); + } else if (user) { + /** + /* authenticated users + **/ + + cannot(Action.DatasetCreate, DatasetClass); + can(Action.DatasetRead, DatasetClass); + cannot(Action.DatasetUpdate, DatasetClass); + // - + cannot(Action.DatasetAttachmentCreate, DatasetClass); + can(Action.DatasetAttachmentRead, DatasetClass); + cannot(Action.DatasetAttachmentUpdate, DatasetClass); + cannot(Action.DatasetAttachmentDelete, DatasetClass); + // - + cannot(Action.DatasetOrigdatablockCreate, DatasetClass); + can(Action.DatasetOrigdatablockRead, DatasetClass); + cannot(Action.DatasetOrigdatablockUpdate, DatasetClass); + // - + cannot(Action.DatasetDatablockCreate, DatasetClass); + can(Action.DatasetDatablockRead, DatasetClass); + cannot(Action.DatasetDatablockUpdate, DatasetClass); + // - + can(Action.DatasetLogbookRead, DatasetClass); + } + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + elasticSearchEndpointAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + + if ( + user && + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ + can(Action.Manage, ElasticSearchActions); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + instrumentEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + cannot(Action.InstrumentRead, Instrument); + cannot(Action.InstrumentCreate, Instrument); + cannot(Action.InstrumentUpdate, Instrument); + cannot(Action.InstrumentDelete, Instrument); + } else { + if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + * user that belongs to any of the group listed in DELETE_GROUPS + */ + + can(Action.InstrumentDelete, Instrument); + } else { + cannot(Action.InstrumentDelete, Instrument); + } + + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /** + * authenticated users belonging to any of the group listed in ADMIN_GROUPS + */ + + can(Action.InstrumentRead, Instrument); + can(Action.InstrumentCreate, Instrument); + can(Action.InstrumentUpdate, Instrument); + } else { + can(Action.InstrumentRead, Instrument); + cannot(Action.InstrumentCreate, Instrument); + cannot(Action.InstrumentUpdate, Instrument); + } + } + + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + jobsEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + /** + * unauthenticated users + */ + + // job creation + if ( + configuration().jobConfiguration.some( + (j) => j.create.auth == CreateJobAuth.All, + ) + ) { + can(Action.JobCreate, JobClass); + } else { + cannot(Action.JobCreate, JobClass); + } + cannot(Action.JobRead, JobClass); + if ( + configuration().jobConfiguration.some( + (j) => j.statusUpdate.auth == StatusUpdateJobAuth.All, + ) + ) { + can(Action.JobStatusUpdate, JobClass); + } else { + cannot(Action.JobStatusUpdate, JobClass); + } + cannot(Action.JobDelete, JobClass); + } else { + /** + * authenticated users + */ + // check if this user is part of the admin group + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /** + * authenticated users belonging to any of the group listed in ADMIN_GROUPS + */ + can(Action.JobRead, JobClass); + can(Action.JobCreate, JobClass); + can(Action.JobStatusUpdate, JobClass); + cannot(Action.JobDelete, JobClass); + } else if ( + user.currentGroups.some((g) => + configuration().deleteJobGroups.includes(g), + ) + ) { + /** + * authenticated users belonging to any of the group listed in DELETE_JOB_GROUPS + */ + can(Action.JobDelete, JobClass); + } else { + const jobUserAuthorizationValues = [ + ...user.currentGroups.map((g) => "@" + g), + user.username, + ]; + if ( + user.currentGroups.some((g) => + configuration().createJobGroups.includes(g), + ) + ) { + /** + * authenticated users belonging to any of the group listed in CREATE_JOBS_GROUPS + */ + can(Action.JobRead, JobClass); + can(Action.JobCreate, JobClass); + } else { + /** + * authenticated users not belonging to any special group + */ + const jobCreateEndPointAuthorizationValues = [ + ...Object.values(CreateJobAuth), + ...jobUserAuthorizationValues, + ]; + can(Action.JobRead, JobClass); + + if ( + configuration().jobConfiguration.some( + (j) => + j.create.auth && + jobCreateEndPointAuthorizationValues.includes( + j.create.auth as string, + ), + ) + ) { + can(Action.JobCreate, JobClass); + } + } + const jobUpdateEndPointAuthorizationValues = [ + ...Object.values(StatusUpdateJobAuth), + ...jobUserAuthorizationValues, + ]; + if ( + user.currentGroups.some((g) => + configuration().statusUpdateJobGroups.includes(g), + ) + ) { + can(Action.JobStatusUpdate, JobClass); + } else { + if ( + configuration().jobConfiguration.some( + (j) => + j.statusUpdate.auth && + jobUpdateEndPointAuthorizationValues.includes( + j.statusUpdate.auth as string, + ), + ) + ) { + can(Action.JobStatusUpdate, JobClass); + } + } + cannot(Action.JobDelete, JobClass); + } + } + + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + logbookEndpointAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (user) { + /* + / authenticated user + */ + can(Action.Read, Logbook); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + origDatablockEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ + + can(Action.OrigdatablockDelete, OrigDatablock); + } else { + /* + / user that does not belong to any of the group listed in DELETE_GROUPS + */ + + cannot(Action.OrigdatablockDelete, OrigDatablock); + } + + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ + + can(Action.OrigdatablockRead, OrigDatablock); + can(Action.OrigdatablockCreate, OrigDatablock); + can(Action.OrigdatablockUpdate, OrigDatablock); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetPrivilegedGroups.includes(g), + ) + ) { + /** + /* users belonging to CREATE_DATASET_PRIVILEGED_GROUPS + **/ + + can(Action.OrigdatablockRead, OrigDatablock); + can(Action.OrigdatablockCreate, OrigDatablock); + can(Action.OrigdatablockUpdate, OrigDatablock); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetWithPidGroups.includes(g), + ) || + configuration().createDatasetWithPidGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_WITH_PID_GROUPS + **/ + + can(Action.OrigdatablockRead, OrigDatablock); + can(Action.OrigdatablockCreate, OrigDatablock); + can(Action.OrigdatablockUpdate, OrigDatablock); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetGroups.includes(g), + ) || + configuration().createDatasetGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_GROUPS + **/ + + can(Action.OrigdatablockRead, OrigDatablock); + can(Action.OrigdatablockCreate, OrigDatablock); + can(Action.OrigdatablockUpdate, OrigDatablock); + } else if (user) { + /** + /* authenticated users + **/ + + can(Action.OrigdatablockRead, OrigDatablock); + cannot(Action.OrigdatablockCreate, OrigDatablock); + cannot(Action.OrigdatablockUpdate, OrigDatablock); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + policyEndpointAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + if ( + user && + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ + can(Action.Delete, Policy); + } else if ( + user && + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ + + can(Action.Update, Policy); + can(Action.Read, Policy); + can(Action.Create, Policy); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + proposalsEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + if (!user) { + /** + * unauthenticated users + */ + + can(Action.ProposalsRead, ProposalClass); + cannot(Action.ProposalsCreate, ProposalClass); + cannot(Action.ProposalsUpdate, ProposalClass); + cannot(Action.ProposalsDelete, ProposalClass); + can(Action.ProposalsAttachmentRead, ProposalClass); + cannot(Action.ProposalsAttachmentCreate, ProposalClass); + cannot(Action.ProposalsAttachmentUpdate, ProposalClass); + cannot(Action.ProposalsAttachmentDelete, ProposalClass); + } else if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ -export type AppAbility = Ability<[AuthOp, Subjects]>; + can(Action.ProposalsDelete, ProposalClass); + } else if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /** + * authenticated users belonging to any of the group listed in ADMIN_GROUPS + */ -@Injectable() -export class CaslAbilityFactory { - createForUser(user: JWTUser) { - const { can, cannot, build } = new AbilityBuilder< - Ability<[AuthOp, Subjects]> - >(Ability as AbilityClass); - - // // admin groups - // const stringAdminGroups = process.env.ADMIN_GROUPS || ""; - // const adminGroups: string[] = stringAdminGroups - // ? stringAdminGroups.split(",").map((v) => v.trim()) - // : []; - // // delete groups - // const stringDeleteGroups = process.env.DELETE_GROUPS || ""; - // const deleteGroups: string[] = stringDeleteGroups - // ? stringDeleteGroups.split(",").map((v) => v.trim()) - // : []; - // // create dataset groups - // const stringCreateDatasetGroups = - // process.env.CREATE_DATASET_GROUPS || "all"; - // const createDatasetGroups: string[] = stringCreateDatasetGroups - // .split(",") - // .map((v) => v.trim()); - - /* - / Set permissions for different type of users for the following subsystems: - / - Datasets (https://scicatproject.github.io/documentation/Development/v4.x/backend/authorization/authorization_datasets.html) - / - OrigDatablocks (https://scicatproject.github.io/documentation/Development/v4.x/backend/authorization/authorization_origdatablocks.html) - */ - if (!user) { + can(Action.ProposalsRead, ProposalClass); + can(Action.ProposalsCreate, ProposalClass); + can(Action.ProposalsUpdate, ProposalClass); + cannot(Action.ProposalsDelete, ProposalClass); + can(Action.ProposalsAttachmentRead, ProposalClass); + can(Action.ProposalsAttachmentCreate, ProposalClass); + can(Action.ProposalsAttachmentUpdate, ProposalClass); + can(Action.ProposalsAttachmentDelete, ProposalClass); + } else if ( + user.currentGroups.some((g) => { + return configuration().proposalGroups.includes(g); + }) + ) { /** - /* unauthenticated users - **/ + * authenticated users belonging to any of the group listed in PROPOSAL_GROUPS + */ + + can(Action.ProposalsRead, ProposalClass); + can(Action.ProposalsCreate, ProposalClass); + can(Action.ProposalsUpdate, ProposalClass); + cannot(Action.ProposalsDelete, ProposalClass); + can(Action.ProposalsAttachmentRead, ProposalClass); + can(Action.ProposalsAttachmentCreate, ProposalClass); + can(Action.ProposalsAttachmentUpdate, ProposalClass); + can(Action.ProposalsAttachmentDelete, ProposalClass); + cannot(Action.ProposalsDatasetRead, ProposalClass); + } else if (user) { + /** + * authenticated users + */ + + can(Action.ProposalsRead, ProposalClass); + cannot(Action.ProposalsCreate, ProposalClass); + cannot(Action.ProposalsUpdate, ProposalClass); + cannot(Action.ProposalsDelete, ProposalClass); + can(Action.ProposalsAttachmentRead, ProposalClass); + cannot(Action.ProposalsAttachmentCreate, ProposalClass); + cannot(Action.ProposalsAttachmentUpdate, ProposalClass); + cannot(Action.ProposalsAttachmentDelete, ProposalClass); + can(Action.ProposalsDatasetRead, ProposalClass); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + publishedDataEndpointAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + if (user) { + can(Action.Read, PublishedData); + can(Action.Update, PublishedData); + can(Action.Create, PublishedData); + } + + if ( + user && + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ + can(Action.Delete, PublishedData); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + samplesEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + if (!user) { // ------------------------------------- - // datasets endpoint authorization - cannot(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - cannot(AuthOp.DatasetUpdate, DatasetClass); - // - - cannot(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - cannot(AuthOp.DatasetAttachmentUpdate, DatasetClass); - cannot(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - cannot(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - cannot(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); - // - - cannot(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - cannot(AuthOp.DatasetDatablockUpdate, DatasetClass); - // - - cannot(AuthOp.DatasetLogbookRead, DatasetClass); + // unauthenticated users // ------------------------------------- - // datasets data instance authorization - can(AuthOp.DatasetReadManyPublic, DatasetClass); - can(AuthOp.DatasetReadOnePublic, DatasetClass, { - isPublished: true, - }); - // - - can(AuthOp.DatasetAttachmentReadPublic, DatasetClass, { - isPublished: true, - }); - // - - can(AuthOp.DatasetOrigdatablockReadPublic, DatasetClass, { - isPublished: true, - }); - // - - can(AuthOp.DatasetDatablockReadPublic, DatasetClass, { - isPublished: true, - }); + can(Action.SampleRead, SampleClass); + cannot(Action.SampleCreate, SampleClass); + cannot(Action.SampleUpdate, SampleClass); + cannot(Action.SampleDelete, SampleClass); + can(Action.SampleAttachmentRead, SampleClass); + cannot(Action.SampleAttachmentCreate, SampleClass); + cannot(Action.SampleAttachmentUpdate, SampleClass); + cannot(Action.SampleAttachmentDelete, SampleClass); + cannot(Action.SampleDatasetRead, SampleClass); + } else { // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - cannot(AuthOp.OrigdatablockCreate, OrigDatablock); - cannot(AuthOp.OrigdatablockUpdate, OrigDatablock); + // authenticated users // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockReadManyAccess, OrigDatablock); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - isPublished: true, - }); - cannot(AuthOp.UserReadOwn, User); - cannot(AuthOp.UserCreateOwn, User); - cannot(AuthOp.UserUpdateOwn, User); - cannot(AuthOp.UserDeleteOwn, User); - cannot(AuthOp.UserReadAny, User); - cannot(AuthOp.UserCreateAny, User); - cannot(AuthOp.UserUpdateAny, User); - cannot(AuthOp.UserDeleteAny, User); - } else { if ( user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) ) { - /* - / user that belongs to any of the group listed in DELETE_GROUPS - */ - - // ------------------------------------- - // datasets // ------------------------------------- - // endpoint authorization - can(AuthOp.DatasetDelete, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockDelete, DatasetClass); - // - - can(AuthOp.DatasetDatablockDelete, DatasetClass); + // users that belong to any of the group listed in DELETE_GROUPS // ------------------------------------- - // data instance authorization - can(AuthOp.DatasetDeleteAny, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockDeleteAny, DatasetClass); - // - - can(AuthOp.DatasetDatablockDeleteAny, DatasetClass); + can(Action.SampleDelete, SampleClass); + can(Action.SampleAttachmentDelete, SampleClass); + } else { // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockDelete, OrigDatablock); + // users that do not belong to any of the group listed in DELETE_GROUPS // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockDeleteAny, OrigDatablock); - can(AuthOp.Delete, PublishedData); - can(AuthOp.Delete, Policy); - } else { - /* - / user that does not belong to any of the group listed in DELETE_GROUPS - */ + cannot(Action.SampleDelete, SampleClass); + } + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { // ------------------------------------- - // datasets + // users belonging to any of the group listed in ADMIN_GROUPS // ------------------------------------- - // endpoint authorization - cannot(AuthOp.DatasetDelete, DatasetClass); - // - - cannot(AuthOp.DatasetOrigdatablockDelete, DatasetClass); - // - - cannot(AuthOp.DatasetDatablockDelete, DatasetClass); + can(Action.SampleRead, SampleClass); + can(Action.SampleCreate, SampleClass); + can(Action.SampleUpdate, SampleClass); + can(Action.SampleAttachmentRead, SampleClass); + can(Action.SampleAttachmentCreate, SampleClass); + can(Action.SampleAttachmentUpdate, SampleClass); + can(Action.SampleAttachmentDelete, SampleClass); + can(Action.SampleDatasetRead, SampleClass); + } else if ( + user.currentGroups.some((g) => + configuration().samplePrivilegedGroups.includes(g), + ) + ) { + // ------------------------------------- + // users belonging to any of the group listed in SAMPLE_GROUPS + // + + can(Action.SampleRead, SampleClass); + can(Action.SampleCreate, SampleClass); + can(Action.SampleUpdate, SampleClass); + can(Action.SampleAttachmentRead, SampleClass); + can(Action.SampleAttachmentCreate, SampleClass); + can(Action.SampleAttachmentUpdate, SampleClass); + can(Action.SampleAttachmentDelete, SampleClass); + can(Action.SampleDatasetRead, SampleClass); + } else if ( + user.currentGroups.some((g) => + configuration().sampleGroups.includes(g), + ) || + configuration().sampleGroups.includes("#all") + ) { + // ------------------------------------- + // users belonging to any of the group listed in SAMPLE_GROUPS + // + + can(Action.SampleRead, SampleClass); + can(Action.SampleCreate, SampleClass); + can(Action.SampleUpdate, SampleClass); + can(Action.SampleAttachmentRead, SampleClass); + can(Action.SampleAttachmentCreate, SampleClass); + can(Action.SampleAttachmentUpdate, SampleClass); + can(Action.SampleAttachmentDelete, SampleClass); + can(Action.SampleDatasetRead, SampleClass); + } else { // ------------------------------------- - // origdatablock + // users with no elevated permissions // ------------------------------------- - // endpoint authorization - cannot(AuthOp.OrigdatablockDelete, OrigDatablock); + + can(Action.SampleRead, SampleClass); + cannot(Action.SampleCreate, SampleClass); + cannot(Action.SampleUpdate, SampleClass); + can(Action.SampleAttachmentRead, SampleClass); + cannot(Action.SampleAttachmentCreate, SampleClass); + cannot(Action.SampleAttachmentUpdate, SampleClass); + if ( + !user.currentGroups.some((g) => + configuration().deleteGroups.includes(g), + ) + ) { + cannot(Action.SampleAttachmentDelete, SampleClass); + } } + } + + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + userEndpointAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + /** + /* unauthenticated users + **/ + + cannot(Action.UserReadOwn, User); + cannot(Action.UserCreateOwn, User); + cannot(Action.UserUpdateOwn, User); + cannot(Action.UserDeleteOwn, User); + cannot(Action.UserReadAny, User); + cannot(Action.UserCreateAny, User); + cannot(Action.UserUpdateAny, User); + cannot(Action.UserDeleteAny, User); + } else { if ( user.currentGroups.some((g) => configuration().adminGroups.includes(g)) ) { @@ -213,87 +847,105 @@ export class CaslAbilityFactory { / user that belongs to any of the group listed in ADMIN_GROUPS */ - // this tests should be all removed, once we are done with authorization review - //can(AuthOp.ListAll, DatasetClass); - // can(AuthOp.ListAll, ProposalClass); - can(AuthOp.ReadAll, UserIdentity); + // can(Action.ReadAll, UserIdentity); NOT used? // ------------------------------------- - // elasticsearch - // ------------------------------------- - // endpoint authorization - can(AuthOp.Manage, ElasticSearchActions); + // user endpoint, including useridentity + can(Action.UserReadAny, User); + can(Action.UserReadOwn, User); + can(Action.UserCreateAny, User); + can(Action.UserUpdateAny, User); + can(Action.UserDeleteAny, User); + can(Action.UserCreateJwt, User); // ------------------------------------- - // datasets - // ------------------------------------- - // endpoint authorization - can(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - can(AuthOp.DatasetUpdate, DatasetClass); - // - - can(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - can(AuthOp.DatasetAttachmentUpdate, DatasetClass); - can(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - can(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); + } else if (user) { + /** + /* authenticated users + **/ + cannot(Action.UserReadAny, User); + cannot(Action.UserCreateAny, User); + cannot(Action.UserUpdateAny, User); + cannot(Action.UserDeleteAny, User); + cannot(Action.UserCreateJwt, User); + } + can(Action.UserReadOwn, User, { _id: user._id }); + can(Action.UserCreateOwn, User, { _id: user._id }); + can(Action.UserUpdateOwn, User, { _id: user._id }); + can(Action.UserDeleteOwn, User, { _id: user._id }); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + datasetInstanceAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + /** + /* unauthenticated users + **/ + + can(Action.DatasetReadManyPublic, DatasetClass); + can(Action.DatasetReadOnePublic, DatasetClass, { + isPublished: true, + }); + // - + can(Action.DatasetAttachmentReadPublic, DatasetClass, { + isPublished: true, + }); + // - + can(Action.DatasetOrigdatablockReadPublic, DatasetClass, { + isPublished: true, + }); + // - + can(Action.DatasetDatablockReadPublic, DatasetClass, { + isPublished: true, + }); + } else { + if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ + + can(Action.DatasetDeleteAny, DatasetClass); // - - can(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - can(AuthOp.DatasetDatablockUpdate, DatasetClass); + can(Action.DatasetOrigdatablockDeleteAny, DatasetClass); // - - can(AuthOp.DatasetLogbookRead, DatasetClass); - // ------------------------------------- - // data instance authorization - can(AuthOp.DatasetCreateAny, DatasetClass); - can(AuthOp.DatasetReadAny, DatasetClass); - can(AuthOp.DatasetUpdateAny, DatasetClass); + can(Action.DatasetDatablockDeleteAny, DatasetClass); + } + + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ + + can(Action.DatasetCreateAny, DatasetClass); + can(Action.DatasetReadAny, DatasetClass); + can(Action.DatasetUpdateAny, DatasetClass); // - - can(AuthOp.DatasetAttachmentCreateAny, DatasetClass); - can(AuthOp.DatasetAttachmentReadAny, DatasetClass); - can(AuthOp.DatasetAttachmentUpdateAny, DatasetClass); - can(AuthOp.DatasetAttachmentDeleteAny, DatasetClass); + can(Action.DatasetAttachmentCreateAny, DatasetClass); + can(Action.DatasetAttachmentReadAny, DatasetClass); + can(Action.DatasetAttachmentUpdateAny, DatasetClass); + can(Action.DatasetAttachmentDeleteAny, DatasetClass); // - - can(AuthOp.DatasetOrigdatablockCreateAny, DatasetClass); - can(AuthOp.DatasetOrigdatablockReadAny, DatasetClass); - can(AuthOp.DatasetOrigdatablockUpdateAny, DatasetClass); + can(Action.DatasetOrigdatablockCreateAny, DatasetClass); + can(Action.DatasetOrigdatablockReadAny, DatasetClass); + can(Action.DatasetOrigdatablockUpdateAny, DatasetClass); // - - can(AuthOp.DatasetDatablockCreateAny, DatasetClass); - can(AuthOp.DatasetDatablockReadAny, DatasetClass); - can(AuthOp.DatasetDatablockUpdateAny, DatasetClass); - // ------------------------------------- - can(AuthOp.DatasetLogbookReadAny, DatasetClass); - - // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - can(AuthOp.OrigdatablockCreate, OrigDatablock); - can(AuthOp.OrigdatablockUpdate, OrigDatablock); - // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockReadAny, OrigDatablock); - can(AuthOp.OrigdatablockCreateAny, OrigDatablock); - can(AuthOp.OrigdatablockUpdateAny, OrigDatablock); - - // ------------------------------------- - // user endpoint, including useridentity - can(AuthOp.UserReadAny, User); - can(AuthOp.UserReadOwn, User); - can(AuthOp.UserCreateAny, User); - can(AuthOp.UserUpdateAny, User); - can(AuthOp.UserDeleteAny, User); - can(AuthOp.UserCreateJwt, User); - + can(Action.DatasetDatablockCreateAny, DatasetClass); + can(Action.DatasetDatablockReadAny, DatasetClass); + can(Action.DatasetDatablockUpdateAny, DatasetClass); // ------------------------------------- - // policies - can(AuthOp.Update, Policy); - can(AuthOp.Read, Policy); - can(AuthOp.Create, Policy); + can(Action.DatasetLogbookReadAny, DatasetClass); } else if ( user.currentGroups.some((g) => configuration().createDatasetPrivilegedGroups.includes(g), @@ -303,115 +955,67 @@ export class CaslAbilityFactory { /* users belonging to CREATE_DATASET_PRIVILEGED_GROUPS **/ - // ------------------------------------- - // datasets - // ------------------------------------- - // endpoint authorization - can(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - can(AuthOp.DatasetUpdate, DatasetClass); - // - - can(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - can(AuthOp.DatasetAttachmentUpdate, DatasetClass); - can(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - can(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - can(AuthOp.DatasetDatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetLogbookRead, DatasetClass); - // ------------------------------------- - // data instance authorization - can(AuthOp.DatasetCreateAny, DatasetClass); - can(AuthOp.DatasetReadManyAccess, DatasetClass); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetCreateAny, DatasetClass); + can(Action.DatasetReadManyAccess, DatasetClass); + can(Action.DatasetReadOneAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetUpdateOwner, DatasetClass, { + can(Action.DatasetUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetAttachmentCreateAny, DatasetClass); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentCreateAny, DatasetClass); + can(Action.DatasetAttachmentReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetAttachmentUpdateOwner, DatasetClass, { + can(Action.DatasetAttachmentUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentDeleteOwner, DatasetClass, { + can(Action.DatasetAttachmentDeleteOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetOrigdatablockCreateAny, DatasetClass); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockCreateAny, DatasetClass); + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetOrigdatablockUpdateOwner, DatasetClass, { + can(Action.DatasetOrigdatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetDatablockCreateAny, DatasetClass); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockCreateAny, DatasetClass); + can(Action.DatasetDatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetDatablockUpdateOwner, DatasetClass, { + can(Action.DatasetDatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetLogbookReadOwner, DatasetClass, { - ownerGroup: { $in: user.currentGroups }, - }); - - // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - can(AuthOp.OrigdatablockCreate, OrigDatablock); - can(AuthOp.OrigdatablockUpdate, OrigDatablock); - // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockCreateAny, OrigDatablock); - can(AuthOp.OrigdatablockReadManyAccess, OrigDatablock); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - ownerGroup: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - accessGroups: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - ownerGroup: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockUpdateOwner, OrigDatablock, { + can(Action.DatasetLogbookReadOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); } else if ( @@ -424,123 +1028,75 @@ export class CaslAbilityFactory { /* users belonging to CREATE_DATASET_WITH_PID_GROUPS **/ - // ------------------------------------- - // datasets endpoint authorization - can(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - can(AuthOp.DatasetUpdate, DatasetClass); - // - - can(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - can(AuthOp.DatasetAttachmentUpdate, DatasetClass); - can(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - can(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - can(AuthOp.DatasetDatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetLogbookRead, DatasetClass); - // ------------------------------------- - // datasets data instance authorization - can(AuthOp.DatasetCreateOwnerWithPid, DatasetClass, { + can(Action.DatasetCreateOwnerWithPid, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadManyAccess, DatasetClass); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadManyAccess, DatasetClass); + can(Action.DatasetReadOneAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetUpdateOwner, DatasetClass, { + can(Action.DatasetUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetAttachmentCreateOwner, DatasetClass, { + can(Action.DatasetAttachmentCreateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetAttachmentUpdateOwner, DatasetClass, { + can(Action.DatasetAttachmentUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentDeleteOwner, DatasetClass, { + can(Action.DatasetAttachmentDeleteOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetOrigdatablockCreateAny, DatasetClass, { + can(Action.DatasetOrigdatablockCreateAny, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetOrigdatablockUpdateOwner, DatasetClass, { + can(Action.DatasetOrigdatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetDatablockCreateAny, DatasetClass, { + can(Action.DatasetDatablockCreateAny, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetDatablockUpdateOwner, DatasetClass, { + can(Action.DatasetDatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetLogbookReadOwner, DatasetClass, { - ownerGroup: { $in: user.currentGroups }, - }); - - // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - can(AuthOp.OrigdatablockCreate, OrigDatablock); - can(AuthOp.OrigdatablockUpdate, OrigDatablock); - // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockCreateOwner, OrigDatablock, { - ownerGroup: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadManyAccess, OrigDatablock); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - ownerGroup: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - accessGroups: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - isPublished: true, - }); - can(AuthOp.OrigdatablockUpdateOwner, OrigDatablock, { + can(Action.DatasetLogbookReadOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); } else if ( @@ -553,125 +1109,77 @@ export class CaslAbilityFactory { /* users belonging to CREATE_DATASET_GROUPS **/ - // ------------------------------------- - // datasets endpoint authorization - can(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - can(AuthOp.DatasetUpdate, DatasetClass); - // - - can(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - can(AuthOp.DatasetAttachmentUpdate, DatasetClass); - can(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - can(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - can(AuthOp.DatasetDatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetLogbookRead, DatasetClass); - // ------------------------------------- - // datasets data instance authorization - can(AuthOp.DatasetCreateOwnerNoPid, DatasetClass, { + can(Action.DatasetCreateOwnerNoPid, DatasetClass, { ownerGroup: { $in: user.currentGroups }, pid: { $eq: "" }, }); - can(AuthOp.DatasetReadManyAccess, DatasetClass); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadManyAccess, DatasetClass); + can(Action.DatasetReadOneAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetUpdateOwner, DatasetClass, { + can(Action.DatasetUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetAttachmentCreateOwner, DatasetClass, { + can(Action.DatasetAttachmentCreateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetAttachmentUpdateOwner, DatasetClass, { + can(Action.DatasetAttachmentUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentDeleteOwner, DatasetClass, { + can(Action.DatasetAttachmentDeleteOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetOrigdatablockCreateAny, DatasetClass, { + can(Action.DatasetOrigdatablockCreateAny, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetOrigdatablockUpdateOwner, DatasetClass, { + can(Action.DatasetOrigdatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); // - - can(AuthOp.DatasetDatablockCreateAny, DatasetClass, { + can(Action.DatasetDatablockCreateAny, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { isPublished: true, }); - can(AuthOp.DatasetDatablockUpdateOwner, DatasetClass, { - ownerGroup: { $in: user.currentGroups }, - }); - // - - can(AuthOp.DatasetLogbookReadOwner, DatasetClass, { - ownerGroup: { $in: user.currentGroups }, - }); - - // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - can(AuthOp.OrigdatablockCreate, OrigDatablock); - can(AuthOp.OrigdatablockUpdate, OrigDatablock); - // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockCreateOwner, OrigDatablock, { - ownerGroup: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadManyAccess, OrigDatablock); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { + can(Action.DatasetDatablockUpdateOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - accessGroups: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - isPublished: true, - }); - can(AuthOp.OrigdatablockUpdateOwner, OrigDatablock, { + // - + can(Action.DatasetLogbookReadOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); } else if (user) { @@ -679,152 +1187,199 @@ export class CaslAbilityFactory { /* authenticated users **/ - // ------------------------------------- - // datasets endpoint authorization - cannot(AuthOp.DatasetCreate, DatasetClass); - can(AuthOp.DatasetRead, DatasetClass); - cannot(AuthOp.DatasetUpdate, DatasetClass); - // - - cannot(AuthOp.DatasetAttachmentCreate, DatasetClass); - can(AuthOp.DatasetAttachmentRead, DatasetClass); - cannot(AuthOp.DatasetAttachmentUpdate, DatasetClass); - cannot(AuthOp.DatasetAttachmentDelete, DatasetClass); - // - - cannot(AuthOp.DatasetOrigdatablockCreate, DatasetClass); - can(AuthOp.DatasetOrigdatablockRead, DatasetClass); - cannot(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); - // - - cannot(AuthOp.DatasetDatablockCreate, DatasetClass); - can(AuthOp.DatasetDatablockRead, DatasetClass); - cannot(AuthOp.DatasetDatablockUpdate, DatasetClass); - // - - can(AuthOp.DatasetLogbookRead, DatasetClass); - // ------------------------------------- - // datasets data instance authorization - can(AuthOp.DatasetReadManyAccess, DatasetClass); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadManyAccess, DatasetClass); + can(Action.DatasetReadOneAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetReadOneAccess, DatasetClass, { + can(Action.DatasetReadOneAccess, DatasetClass, { isPublished: true, }); // - - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetAttachmentReadAccess, DatasetClass, { + can(Action.DatasetAttachmentReadAccess, DatasetClass, { isPublished: true, }); // - - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetOrigdatablockReadAccess, DatasetClass, { + can(Action.DatasetOrigdatablockReadAccess, DatasetClass, { isPublished: true, }); // - - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.DatasetDatablockReadAccess, DatasetClass, { + can(Action.DatasetDatablockReadAccess, DatasetClass, { isPublished: true, }); // - - can(AuthOp.DatasetLogbookReadOwner, DatasetClass, { - ownerGroup: { $in: user.currentGroups }, - }); - - // ------------------------------------- - // origdatablock - // ------------------------------------- - // endpoint authorization - can(AuthOp.OrigdatablockRead, OrigDatablock); - cannot(AuthOp.OrigdatablockCreate, OrigDatablock); - cannot(AuthOp.OrigdatablockUpdate, OrigDatablock); - // ------------------------------------- - // data instance authorization - can(AuthOp.OrigdatablockReadManyAccess, OrigDatablock); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { + can(Action.DatasetLogbookReadOwner, DatasetClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - accessGroups: { $in: user.currentGroups }, - }); - can(AuthOp.OrigdatablockReadOneAccess, OrigDatablock, { - isPublished: true, - }); - cannot(AuthOp.UserReadAny, User); - cannot(AuthOp.UserCreateAny, User); - cannot(AuthOp.UserUpdateAny, User); - cannot(AuthOp.UserDeleteAny, User); - cannot(AuthOp.UserCreateJwt, User); } - can(AuthOp.UserReadOwn, User, { _id: user._id }); - can(AuthOp.UserCreateOwn, User, { _id: user._id }); - can(AuthOp.UserUpdateOwn, User, { _id: user._id }); - can(AuthOp.UserDeleteOwn, User, { _id: user._id }); } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + origDatablockInstanceAccess(user: JWTUser) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + if ( + user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in DELETE_GROUPS + */ - // ************************************ - // JOBS AUTHORIZATION - // ************************************ + can(Action.OrigdatablockDeleteAny, OrigDatablock); + } + if ( + user.currentGroups.some((g) => configuration().adminGroups.includes(g)) + ) { + /* + / user that belongs to any of the group listed in ADMIN_GROUPS + */ - if (!user) { + can(Action.OrigdatablockReadAny, OrigDatablock); + can(Action.OrigdatablockCreateAny, OrigDatablock); + can(Action.OrigdatablockUpdateAny, OrigDatablock); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetPrivilegedGroups.includes(g), + ) + ) { /** - * unauthenticated users - */ + /* users belonging to CREATE_DATASET_PRIVILEGED_GROUPS + **/ + can(Action.OrigdatablockCreateAny, OrigDatablock); + can(Action.OrigdatablockReadManyAccess, OrigDatablock); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + accessGroups: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockUpdateOwner, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetWithPidGroups.includes(g), + ) || + configuration().createDatasetWithPidGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_WITH_PID_GROUPS + **/ - // ------------------------------------- - // loop through the jobs definition and extract the most permissive permission - // ------------------------------------- - // endpoint authorization - // job creation - if ( - configuration().jobConfiguration.some( - (j) => j.create.auth == CreateJobAuth.All, - ) - ) { - can(AuthOp.JobCreate, JobClass); - } else { - cannot(AuthOp.JobCreate, JobClass); - } - cannot(AuthOp.JobRead, JobClass); - if ( - configuration().jobConfiguration.some( - (j) => j.statusUpdate.auth == StatusUpdateJobAuth.All, - ) - ) { - can(AuthOp.JobStatusUpdate, JobClass); - } else { - cannot(AuthOp.JobStatusUpdate, JobClass); - } - cannot(AuthOp.JobDelete, JobClass); + can(Action.OrigdatablockCreateOwner, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadManyAccess, OrigDatablock); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + accessGroups: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + isPublished: true, + }); + can(Action.OrigdatablockUpdateOwner, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + } else if ( + user.currentGroups.some((g) => + configuration().createDatasetGroups.includes(g), + ) || + configuration().createDatasetGroups.includes("#all") + ) { + /** + /* users belonging to CREATE_DATASET_GROUPS + **/ - // ------------------------------------- - // instance authorization - can(AuthOp.JobCreateConfiguration, JobClass, { - ["configuration.create.auth" as string]: CreateJobAuth.All, + can(Action.OrigdatablockCreateOwner, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadManyAccess, OrigDatablock); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + accessGroups: { $in: user.currentGroups }, + }); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + isPublished: true, + }); + can(Action.OrigdatablockUpdateOwner, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, + }); + } else if (user) { + /** + /* authenticated users + **/ + + can(Action.OrigdatablockReadManyAccess, OrigDatablock); + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.JobCreateConfiguration, JobClass, { - ["configuration.create.auth" as string]: CreateJobAuth.DatasetPublic, - datasetsValidation: true, + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.JobStatusUpdateConfiguration, JobClass, { - ["configuration.statusUpdate.auth" as string]: StatusUpdateJobAuth.All, - ownerGroup: undefined, + can(Action.OrigdatablockReadOneAccess, OrigDatablock, { + isPublished: true, }); + } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + jobsInstanceAccess(user: JWTUser, jobConfiguration: JobConfig) { + const { can, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (!user) { + /** + * unauthenticated users + */ + if (jobConfiguration.create.auth === CreateJobAuth.All) { + can(Action.JobCreateConfiguration, JobClass); + } + if (jobConfiguration.create.auth === CreateJobAuth.DatasetPublic) { + can(Action.JobCreateConfiguration, JobClass, { + datasetsValidation: true, + }); + } + if (jobConfiguration.statusUpdate.auth === StatusUpdateJobAuth.All) { + can(Action.JobStatusUpdateConfiguration, JobClass, { + ownerGroup: undefined, + }); + } } else { /** * authenticated users @@ -836,33 +1391,9 @@ export class CaslAbilityFactory { /** * authenticated users belonging to any of the group listed in ADMIN_GROUPS */ - // ------------------------------------- - // endpoint authorization - can(AuthOp.JobRead, JobClass); - can(AuthOp.JobCreate, JobClass); - can(AuthOp.JobStatusUpdate, JobClass); - cannot(AuthOp.JobDelete, JobClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobReadAny, JobClass); - can(AuthOp.JobCreateAny, JobClass); - can(AuthOp.JobStatusUpdateAny, JobClass); - } else if ( - user.currentGroups.some((g) => - configuration().deleteJobGroups.includes(g), - ) - ) { - /** - * authenticated users belonging to any of the group listed in DELETE_JOB_GROUPS - */ - // ------------------------------------- - // endpoint authorization - can(AuthOp.JobDelete, JobClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobDeleteAny, JobClass); + can(Action.JobReadAny, JobClass); + can(Action.JobCreateAny, JobClass); + can(Action.JobStatusUpdateAny, JobClass); } else { const jobUserAuthorizationValues = [ ...user.currentGroups.map((g) => "@" + g), @@ -877,27 +1408,16 @@ export class CaslAbilityFactory { * authenticated users belonging to any of the group listed in CREATE_JOBS_GROUPS */ - // ------------------------------------- - // endpoint authorization - can(AuthOp.JobRead, JobClass); - can(AuthOp.JobCreate, JobClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobCreateOwner, JobClass, { + can(Action.JobCreateOwner, JobClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.JobReadAccess, JobClass, { + can(Action.JobReadAccess, JobClass, { ownerGroup: { $in: user.currentGroups }, }); } else { /** * authenticated users not belonging to any special group */ - const jobCreateEndPointAuthorizationValues = [ - ...Object.values(CreateJobAuth), - ...jobUserAuthorizationValues, - ]; const jobCreateInstanceAuthorizationValues = [ ...Object.values(CreateJobAuth).filter( (v) => !String(v).includes("#dataset"), @@ -909,45 +1429,28 @@ export class CaslAbilityFactory { String(v).includes("#dataset"), ), ]; - // ------------------------------------- - // endpoint authorization - can(AuthOp.JobRead, JobClass); + can(Action.JobReadAccess, JobClass, { + ownerGroup: { $in: user.currentGroups }, + ownerUser: user.username, + }); if ( - configuration().jobConfiguration.some( - (j) => - j.create.auth && - jobCreateEndPointAuthorizationValues.includes( - j.create.auth as string, - ), + jobCreateInstanceAuthorizationValues.some( + (a) => jobConfiguration.create.auth === a, ) ) { - can(AuthOp.JobCreate, JobClass); + can(Action.JobCreateConfiguration, JobClass); + } + if ( + jobCreateDatasetAuthorizationValues.some( + (a) => jobConfiguration.create.auth === a, + ) + ) { + can(Action.JobCreateConfiguration, JobClass, { + datasetsValidation: true, + }); } - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobReadAccess, JobClass, { - ownerGroup: { $in: user.currentGroups }, - ownerUser: user.username, - }); - - can(AuthOp.JobCreateConfiguration, JobClass, { - ["configuration.create.auth" as string]: { - $in: jobCreateInstanceAuthorizationValues, - }, - }); - can(AuthOp.JobCreateConfiguration, JobClass, { - ["configuration.create.auth" as string]: { - $in: jobCreateDatasetAuthorizationValues, - }, - datasetsValidation: true, - }); } - const jobUpdateEndPointAuthorizationValues = [ - ...Object.values(StatusUpdateJobAuth), - ...jobUserAuthorizationValues, - ]; const jobUpdateInstanceAuthorizationValues = [ ...Object.values(StatusUpdateJobAuth).filter( (v) => !String(v).includes("#job"), @@ -960,87 +1463,61 @@ export class CaslAbilityFactory { configuration().statusUpdateJobGroups.includes(g), ) ) { - // ------------------------------------- - // endpoint authorization - can(AuthOp.JobStatusUpdate, JobClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobStatusUpdateConfiguration, JobClass, { - ["configuration.statusUpdate.auth" as string]: { - $in: jobUpdateInstanceAuthorizationValues, - }, - }); - can(AuthOp.JobStatusUpdateOwner, JobClass, { + if ( + jobUpdateInstanceAuthorizationValues.some( + (a) => jobConfiguration.statusUpdate.auth === a, + ) + ) { + can(Action.JobStatusUpdateConfiguration, JobClass); + } + can(Action.JobStatusUpdateOwner, JobClass, { ownerUser: user.username, }); - can(AuthOp.JobStatusUpdateOwner, JobClass, { + can(Action.JobStatusUpdateOwner, JobClass, { ownerGroup: { $in: user.currentGroups }, }); } else { - // ------------------------------------- - // endpoint authorization if ( - configuration().jobConfiguration.some( - (j) => - j.statusUpdate.auth && - jobUpdateEndPointAuthorizationValues.includes( - j.statusUpdate.auth as string, - ), + jobUpdateInstanceAuthorizationValues.some( + (a) => jobConfiguration.statusUpdate.auth === a, ) ) { - can(AuthOp.JobStatusUpdate, JobClass); + can(Action.JobStatusUpdateConfiguration, JobClass); + } + if (jobConfiguration.statusUpdate.auth === "#jobOwnerUser") { + can(Action.JobStatusUpdateConfiguration, JobClass, { + ownerUser: user.username, + }); + } + if (jobConfiguration.statusUpdate.auth === "#jobOwnerGroup") { + can(Action.JobStatusUpdateConfiguration, JobClass, { + ownerGroup: { $in: user.currentGroups }, + }); } - - // ------------------------------------- - // data instance authorization - can(AuthOp.JobStatusUpdateConfiguration, JobClass, { - ["configuration.statusUpdate.auth" as string]: { - $in: jobUpdateInstanceAuthorizationValues, - }, - }); - can(AuthOp.JobStatusUpdateConfiguration, JobClass, { - ["configuration.statusUpdate.auth" as string]: "#jobOwnerUser", - ownerUser: user.username, - }); - can(AuthOp.JobStatusUpdateConfiguration, JobClass, { - ["configuration.statusUpdate.auth" as string]: "#jobOwnerGroup", - ownerGroup: { $in: user.currentGroups }, - }); } - cannot(AuthOp.JobDelete, JobClass); } } - // ************************************ - // PROPOSALS AUTHORIZATION - // ************************************ + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + proposalsInstanceAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); if (!user) { /** * unauthenticated users */ - // ------------------------------------- - // proposals - // ------------------------------------- - // endpoint authorization - can(AuthOp.ProposalsRead, ProposalClass); - cannot(AuthOp.ProposalsCreate, ProposalClass); - cannot(AuthOp.ProposalsUpdate, ProposalClass); - cannot(AuthOp.ProposalsDelete, ProposalClass); - can(AuthOp.ProposalsAttachmentRead, ProposalClass); - cannot(AuthOp.ProposalsAttachmentCreate, ProposalClass); - cannot(AuthOp.ProposalsAttachmentUpdate, ProposalClass); - cannot(AuthOp.ProposalsAttachmentDelete, ProposalClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.ProposalsReadManyPublic, ProposalClass); - can(AuthOp.ProposalsReadOnePublic, ProposalClass, { + can(Action.ProposalsReadManyPublic, ProposalClass); + can(Action.ProposalsReadOnePublic, ProposalClass, { isPublished: true, }); - can(AuthOp.ProposalsAttachmentReadPublic, ProposalClass, { + can(Action.ProposalsAttachmentReadPublic, ProposalClass, { isPublished: true, }); } else if ( @@ -1049,17 +1526,7 @@ export class CaslAbilityFactory { /* / user that belongs to any of the group listed in DELETE_GROUPS */ - - // ------------------------------------- - // proposals - // ------------------------------------- - // endpoint authorization - can(AuthOp.ProposalsDelete, ProposalClass); - - // ------------------------------------- - // data instance authorization - - can(AuthOp.ProposalsDeleteAny, ProposalClass); + can(Action.ProposalsDeleteAny, ProposalClass); } else if ( user.currentGroups.some((g) => configuration().adminGroups.includes(g)) ) { @@ -1067,28 +1534,14 @@ export class CaslAbilityFactory { * authenticated users belonging to any of the group listed in ADMIN_GROUPS */ - // ------------------------------------- - // proposals - // ------------------------------------- - // endpoint authorization - can(AuthOp.ProposalsRead, ProposalClass); - can(AuthOp.ProposalsCreate, ProposalClass); - can(AuthOp.ProposalsUpdate, ProposalClass); - cannot(AuthOp.ProposalsDelete, ProposalClass); - can(AuthOp.ProposalsAttachmentRead, ProposalClass); - can(AuthOp.ProposalsAttachmentCreate, ProposalClass); - can(AuthOp.ProposalsAttachmentUpdate, ProposalClass); - can(AuthOp.ProposalsAttachmentDelete, ProposalClass); - // ------------------------------------- - // data instance authorization - can(AuthOp.ProposalsReadAny, ProposalClass); - can(AuthOp.ProposalsCreateAny, ProposalClass); - can(AuthOp.ProposalsUpdateAny, ProposalClass); - cannot(AuthOp.ProposalsDeleteAny, ProposalClass); - can(AuthOp.ProposalsAttachmentReadAny, ProposalClass); - can(AuthOp.ProposalsAttachmentCreateAny, ProposalClass); - can(AuthOp.ProposalsAttachmentUpdateAny, ProposalClass); - can(AuthOp.ProposalsAttachmentDeleteAny, ProposalClass); + can(Action.ProposalsReadAny, ProposalClass); + can(Action.ProposalsCreateAny, ProposalClass); + can(Action.ProposalsUpdateAny, ProposalClass); + cannot(Action.ProposalsDeleteAny, ProposalClass); + can(Action.ProposalsAttachmentReadAny, ProposalClass); + can(Action.ProposalsAttachmentCreateAny, ProposalClass); + can(Action.ProposalsAttachmentUpdateAny, ProposalClass); + can(Action.ProposalsAttachmentDeleteAny, ProposalClass); } else if ( user.currentGroups.some((g) => { return configuration().proposalGroups.includes(g); @@ -1098,48 +1551,32 @@ export class CaslAbilityFactory { * authenticated users belonging to any of the group listed in PROPOSAL_GROUPS */ - // ------------------------------------- - // proposals - // ------------------------------------- - // endpoint authorization - - can(AuthOp.ProposalsRead, ProposalClass); - can(AuthOp.ProposalsCreate, ProposalClass); - can(AuthOp.ProposalsUpdate, ProposalClass); - cannot(AuthOp.ProposalsDelete, ProposalClass); - can(AuthOp.ProposalsAttachmentRead, ProposalClass); - can(AuthOp.ProposalsAttachmentCreate, ProposalClass); - can(AuthOp.ProposalsAttachmentUpdate, ProposalClass); - can(AuthOp.ProposalsAttachmentDelete, ProposalClass); - cannot(AuthOp.ProposalsDatasetRead, ProposalClass); - // ------------------------------------- - // data instance authorization - can(AuthOp.ProposalsCreateAny, ProposalClass); - can(AuthOp.ProposalsReadManyAccess, ProposalClass); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsCreateAny, ProposalClass); + can(Action.ProposalsReadManyAccess, ProposalClass); + can(Action.ProposalsReadOneAccess, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsReadOneAccess, ProposalClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsReadOneAccess, ProposalClass, { isPublished: true, }); //- - can(AuthOp.ProposalsAttachmentCreateAny, ProposalClass); - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentCreateAny, ProposalClass); + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { isPublished: true, }); - can(AuthOp.ProposalsAttachmentUpdateOwner, ProposalClass, { + can(Action.ProposalsAttachmentUpdateOwner, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsAttachmentDeleteOwner, ProposalClass, { + can(Action.ProposalsAttachmentDeleteOwner, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); } else if (user) { @@ -1147,70 +1584,48 @@ export class CaslAbilityFactory { * authenticated users */ - // ------------------------------------- - // proposals - // ------------------------------------- - // endpoint authorization - can(AuthOp.ProposalsRead, ProposalClass); - cannot(AuthOp.ProposalsCreate, ProposalClass); - cannot(AuthOp.ProposalsUpdate, ProposalClass); - cannot(AuthOp.ProposalsDelete, ProposalClass); - can(AuthOp.ProposalsAttachmentRead, ProposalClass); - cannot(AuthOp.ProposalsAttachmentCreate, ProposalClass); - cannot(AuthOp.ProposalsAttachmentUpdate, ProposalClass); - cannot(AuthOp.ProposalsAttachmentDelete, ProposalClass); - can(AuthOp.ProposalsDatasetRead, ProposalClass); - // ------------------------------------- - // data instance authorization - can(AuthOp.ProposalsReadManyAccess, ProposalClass); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsReadManyAccess, ProposalClass); + can(Action.ProposalsReadOneAccess, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsReadOneAccess, ProposalClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsReadOneAccess, ProposalClass, { + can(Action.ProposalsReadOneAccess, ProposalClass, { isPublished: true, }); // - - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.ProposalsAttachmentReadAccess, ProposalClass, { + can(Action.ProposalsAttachmentReadAccess, ProposalClass, { isPublished: true, }); } + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } + + samplesInstanceAccess(user: JWTUser) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); - // ************************************ - // SAMPLES AUTHORIZATION - // ************************************ if (!user) { // ------------------------------------- // unauthenticated users // ------------------------------------- - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleRead, SampleClass); - cannot(AuthOp.SampleCreate, SampleClass); - cannot(AuthOp.SampleUpdate, SampleClass); - cannot(AuthOp.SampleDelete, SampleClass); - can(AuthOp.SampleAttachmentRead, SampleClass); - cannot(AuthOp.SampleAttachmentCreate, SampleClass); - cannot(AuthOp.SampleAttachmentUpdate, SampleClass); - cannot(AuthOp.SampleAttachmentDelete, SampleClass); - cannot(AuthOp.SampleDatasetRead, SampleClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleReadManyPublic, SampleClass); - can(AuthOp.SampleReadOnePublic, SampleClass, { + can(Action.SampleReadManyPublic, SampleClass); + can(Action.SampleReadOnePublic, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentReadPublic, SampleClass, { + can(Action.SampleAttachmentReadPublic, SampleClass, { isPublished: true, }); } else { @@ -1225,28 +1640,15 @@ export class CaslAbilityFactory { // users that belong to any of the group listed in DELETE_GROUPS // ------------------------------------- - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleDelete, SampleClass); - can(AuthOp.SampleAttachmentDelete, SampleClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleDeleteAny, SampleClass); - can(AuthOp.SampleAttachmentDeleteAny, SampleClass); + can(Action.SampleDeleteAny, SampleClass); + can(Action.SampleAttachmentDeleteAny, SampleClass); } else { // ------------------------------------- // users that do not belong to any of the group listed in DELETE_GROUPS // ------------------------------------- - // ------------------------------------- - // endpoint authorization - cannot(AuthOp.SampleDelete, SampleClass); - - // ------------------------------------- - // data instance authorization - cannot(AuthOp.SampleDeleteAny, SampleClass); - cannot(AuthOp.SampleDeleteOwner, SampleClass); + cannot(Action.SampleDeleteAny, SampleClass); + cannot(Action.SampleDeleteOwner, SampleClass); } if ( @@ -1256,26 +1658,13 @@ export class CaslAbilityFactory { // users belonging to any of the group listed in ADMIN_GROUPS // ------------------------------------- - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleRead, SampleClass); - can(AuthOp.SampleCreate, SampleClass); - can(AuthOp.SampleUpdate, SampleClass); - can(AuthOp.SampleAttachmentRead, SampleClass); - can(AuthOp.SampleAttachmentCreate, SampleClass); - can(AuthOp.SampleAttachmentUpdate, SampleClass); - can(AuthOp.SampleAttachmentDelete, SampleClass); - can(AuthOp.SampleDatasetRead, SampleClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleReadAny, SampleClass); - can(AuthOp.SampleCreateAny, SampleClass); - can(AuthOp.SampleUpdateAny, SampleClass); - can(AuthOp.SampleAttachmentReadAny, SampleClass); - can(AuthOp.SampleAttachmentCreateAny, SampleClass); - can(AuthOp.SampleAttachmentUpdateAny, SampleClass); - can(AuthOp.SampleAttachmentDeleteAny, SampleClass); + can(Action.SampleReadAny, SampleClass); + can(Action.SampleCreateAny, SampleClass); + can(Action.SampleUpdateAny, SampleClass); + can(Action.SampleAttachmentReadAny, SampleClass); + can(Action.SampleAttachmentCreateAny, SampleClass); + can(Action.SampleAttachmentUpdateAny, SampleClass); + can(Action.SampleAttachmentDeleteAny, SampleClass); } else if ( user.currentGroups.some((g) => configuration().samplePrivilegedGroups.includes(g), @@ -1285,47 +1674,34 @@ export class CaslAbilityFactory { // users belonging to any of the group listed in SAMPLE_GROUPS // - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleRead, SampleClass); - can(AuthOp.SampleCreate, SampleClass); - can(AuthOp.SampleUpdate, SampleClass); - can(AuthOp.SampleAttachmentRead, SampleClass); - can(AuthOp.SampleAttachmentCreate, SampleClass); - can(AuthOp.SampleAttachmentUpdate, SampleClass); - can(AuthOp.SampleAttachmentDelete, SampleClass); - can(AuthOp.SampleDatasetRead, SampleClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleCreateAny, SampleClass); - can(AuthOp.SampleUpdateOwner, SampleClass, { + can(Action.SampleCreateAny, SampleClass); + can(Action.SampleUpdateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadManyAccess, SampleClass); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadManyAccess, SampleClass); + can(Action.SampleReadOneAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentCreateAny, SampleClass); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentCreateAny, SampleClass); + can(Action.SampleAttachmentReadAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentUpdateOwner, SampleClass, { + can(Action.SampleAttachmentUpdateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentDeleteOwner, SampleClass, { + can(Action.SampleAttachmentDeleteOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); } else if ( @@ -1338,51 +1714,38 @@ export class CaslAbilityFactory { // users belonging to any of the group listed in SAMPLE_GROUPS // - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleRead, SampleClass); - can(AuthOp.SampleCreate, SampleClass); - can(AuthOp.SampleUpdate, SampleClass); - can(AuthOp.SampleAttachmentRead, SampleClass); - can(AuthOp.SampleAttachmentCreate, SampleClass); - can(AuthOp.SampleAttachmentUpdate, SampleClass); - can(AuthOp.SampleAttachmentDelete, SampleClass); - can(AuthOp.SampleDatasetRead, SampleClass); - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleCreateOwner, SampleClass, { + can(Action.SampleCreateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleUpdateOwner, SampleClass, { + can(Action.SampleUpdateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadManyAccess, SampleClass); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadManyAccess, SampleClass); + can(Action.SampleReadOneAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentCreateOwner, SampleClass, { + can(Action.SampleAttachmentCreateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentUpdateOwner, SampleClass, { + can(Action.SampleAttachmentUpdateOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentDeleteOwner, SampleClass, { + can(Action.SampleAttachmentDeleteOwner, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); } else { @@ -1390,160 +1753,28 @@ export class CaslAbilityFactory { // users with no elevated permissions // ------------------------------------- - // ------------------------------------- - // endpoint authorization - can(AuthOp.SampleRead, SampleClass); - cannot(AuthOp.SampleCreate, SampleClass); - cannot(AuthOp.SampleUpdate, SampleClass); - can(AuthOp.SampleAttachmentRead, SampleClass); - cannot(AuthOp.SampleAttachmentCreate, SampleClass); - cannot(AuthOp.SampleAttachmentUpdate, SampleClass); - if ( - !user.currentGroups.some((g) => - configuration().deleteGroups.includes(g), - ) - ) { - cannot(AuthOp.SampleAttachmentDelete, SampleClass); - } - - // ------------------------------------- - // data instance authorization - can(AuthOp.SampleReadManyAccess, SampleClass); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadManyAccess, SampleClass); + can(Action.SampleReadOneAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleReadOneAccess, SampleClass, { + can(Action.SampleReadOneAccess, SampleClass, { isPublished: true, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { ownerGroup: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { accessGroups: { $in: user.currentGroups }, }); - can(AuthOp.SampleAttachmentReadAccess, SampleClass, { + can(Action.SampleAttachmentReadAccess, SampleClass, { isPublished: true, }); } } - // ************************************ - // INSTRUMENT AUTHORIZATION - // ************************************ - - if (!user) { - cannot(AuthOp.InstrumentRead, Instrument); - cannot(AuthOp.InstrumentCreate, Instrument); - cannot(AuthOp.InstrumentUpdate, Instrument); - cannot(AuthOp.InstrumentDelete, Instrument); - } else if ( - user.currentGroups.some((g) => configuration().deleteGroups.includes(g)) - ) { - /* - / user that belongs to any of the group listed in DELETE_GROUPS - */ - - // ------------------------------------- - // instrument - // ------------------------------------- - // endpoint authorization - can(AuthOp.InstrumentDelete, Instrument); - } else if ( - user.currentGroups.some((g) => configuration().adminGroups.includes(g)) - ) { - /** - * authenticated users belonging to any of the group listed in ADMIN_GROUPS - */ - // ------------------------------------- - // instrument - // ------------------------------------- - // endpoint authorization - can(AuthOp.InstrumentRead, Instrument); - can(AuthOp.InstrumentCreate, Instrument); - can(AuthOp.InstrumentUpdate, Instrument); - cannot(AuthOp.InstrumentDelete, Instrument); - } else if (user) { - can(AuthOp.InstrumentRead, Instrument); - cannot(AuthOp.InstrumentCreate, Instrument); - cannot(AuthOp.InstrumentUpdate, Instrument); - cannot(AuthOp.InstrumentDelete, Instrument); - } - - // Instrument permissions - //can(AuthOp.Read, Instrument); - //if (user.currentGroups.some((g) => adminGroups.includes(g))) { - // can(AuthOp.Manage, Instrument); - //} - - //can(AuthOp.Manage, JobClass); - - can(AuthOp.Read, Logbook); - - can(AuthOp.Read, PublishedData); - can(AuthOp.Update, PublishedData); - can(AuthOp.Create, PublishedData); - - // can(AuthOp.Manage, Attachment, { - // ownerGroup: { $in: user.currentGroups }, - // }); - // can(AuthOp.Manage, Datablock, { - // ownerGroup: { $in: user.currentGroups }, - // }); - // can(AuthOp.Manage, OrigDatablock, { - // ownerGroup: { $in: user.currentGroups }, - // }); - - // if (user.currentGroups.includes(Role.Admin)) { - // can(AuthOp.Manage, "all"); - // } - // if (user.currentGroups.includes(Role.ArchiveManager)) { - // //cannot(AuthOp.Create, DatasetClass); - // //cannot(AuthOp.Update, DatasetClass); - // //can(AuthOp.Delete, DatasetClass); - // cannot(AuthOp.Manage, OrigDatablock); - // cannot(AuthOp.Create, OrigDatablock); - // cannot(AuthOp.Update, OrigDatablock); - // can(AuthOp.Delete, OrigDatablock); - // cannot(AuthOp.Manage, Datablock); - // cannot(AuthOp.Create, Datablock); - // cannot(AuthOp.Update, Datablock); - // can(AuthOp.Delete, Datablock); - // can(AuthOp.Delete, PublishedData); - // //-------------------------------- - // // instrument - // cannot(AuthOp.InstrumentRead, Instrument); - // cannot(AuthOp.InstrumentCreate, Instrument); - // cannot(AuthOp.InstrumentUpdate, Instrument); - // can(AuthOp.InstrumentDelete, Instrument); - // } - //if (user.currentGroups.includes(Role.GlobalAccess)) { - // can(AuthOp.Read, "all"); - //} - // if (user.currentGroups.includes(Role.Ingestor)) { - // can(AuthOp.Create, Attachment); - - // //cannot(AuthOp.Delete, DatasetClass); - // //can(AuthOp.Create, DatasetClass); - // //can(AuthOp.Update, DatasetClass); - - // can(AuthOp.Create, Instrument); - // can(AuthOp.Update, Instrument); - // } - // if (user.currentGroups.includes(Role.ProposalIngestor)) { - // cannot(AuthOp.Delete, ProposalClass); - // can(AuthOp.Create, ProposalClass); - // can(AuthOp.Update, ProposalClass); - // can(AuthOp.Read, ProposalClass); - // can(AuthOp.ListAll, ProposalClass); - // } - - //can(AuthOp.Create, UserSettings, { userId: user._id }); - //can(AuthOp.Read, UserSettings, { userId: user._id }); - //can(AuthOp.Update, UserSettings, { userId: user._id }); - return build({ detectSubjectType: (item) => item.constructor as ExtractSubjectType, diff --git a/src/casl/decorators/check-policies.decorator.ts b/src/casl/decorators/check-policies.decorator.ts index 6ac4f3550..ec4af992a 100644 --- a/src/casl/decorators/check-policies.decorator.ts +++ b/src/casl/decorators/check-policies.decorator.ts @@ -3,5 +3,5 @@ import { PolicyHandler } from "../interfaces/policy-handler.interface"; export const CHECK_POLICIES_KEY = "check_policy"; -export const CheckPolicies = (...handlers: PolicyHandler[]) => - SetMetadata(CHECK_POLICIES_KEY, handlers); +export const CheckPolicies = (endpoint: string, ...handlers: PolicyHandler[]) => + SetMetadata(CHECK_POLICIES_KEY, { endpoint, handlers }); diff --git a/src/casl/guards/policies.guard.ts b/src/casl/guards/policies.guard.ts index 17ff148ce..94737825a 100644 --- a/src/casl/guards/policies.guard.ts +++ b/src/casl/guards/policies.guard.ts @@ -12,15 +12,21 @@ export class PoliciesGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const policyHandlers = - this.reflector.get( - CHECK_POLICIES_KEY, - context.getHandler(), - ) || []; + const policyData = this.reflector.get<{ + endpoint: string; + handlers: PolicyHandler[]; + }>(CHECK_POLICIES_KEY, context.getHandler()); + if (!policyData) { + return false; + } + + const policyHandlers = policyData["handlers"]; + const endpoint = policyData["endpoint"]; const req = context.switchToHttp().getRequest(); const user = req.user; - const ability = this.caslAbilityFactory.createForUser(user); + + const ability = this.caslAbilityFactory.endpointAccess(endpoint, user); return policyHandlers.every((handler) => this.execPolicyHandler(handler, ability), ); diff --git a/src/datasets/datasets.controller.ts b/src/datasets/datasets.controller.ts index 79e1a0fc9..0d7706ee1 100644 --- a/src/datasets/datasets.controller.ts +++ b/src/datasets/datasets.controller.ts @@ -43,7 +43,7 @@ import { CreateDerivedDatasetDto } from "./dto/create-derived-dataset.dto"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { IDatasetFields } from "./interfaces/dataset-filters.interface"; import { MainDatasetsPublicInterceptor, @@ -160,15 +160,15 @@ export class DatasetsController { ): IFilters { const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); - const canViewOwner = ability.can(AuthOp.DatasetReadManyOwner, DatasetClass); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); + const canViewOwner = ability.can(Action.DatasetReadManyOwner, DatasetClass); const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewPublic = ability.can( - AuthOp.DatasetReadManyPublic, + Action.DatasetReadManyPublic, DatasetClass, ); @@ -196,7 +196,7 @@ export class DatasetsController { async checkPermissionsForDatasetExtended( request: Request, id: string, - group: AuthOp, + group: Action, ) { const dataset = await this.datasetsService.findOne({ where: { pid: id } }); const user: JWTUser = request.user as JWTUser; @@ -205,74 +205,74 @@ export class DatasetsController { const datasetInstance = await this.generateDatasetInstanceForPermissions(dataset); - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); let canDoAction = false; - if (group == AuthOp.DatasetRead) { + if (group == Action.DatasetRead) { canDoAction = - ability.can(AuthOp.DatasetReadAny, DatasetClass) || - ability.can(AuthOp.DatasetReadOneOwner, datasetInstance) || - ability.can(AuthOp.DatasetReadOneAccess, datasetInstance) || - ability.can(AuthOp.DatasetReadOnePublic, datasetInstance); - } else if (group == AuthOp.DatasetAttachmentRead) { + ability.can(Action.DatasetReadAny, DatasetClass) || + ability.can(Action.DatasetReadOneOwner, datasetInstance) || + ability.can(Action.DatasetReadOneAccess, datasetInstance) || + ability.can(Action.DatasetReadOnePublic, datasetInstance); + } else if (group == Action.DatasetAttachmentRead) { canDoAction = - ability.can(AuthOp.DatasetAttachmentReadAny, DatasetClass) || - ability.can(AuthOp.DatasetAttachmentReadOwner, datasetInstance) || - ability.can(AuthOp.DatasetAttachmentReadAccess, datasetInstance) || - ability.can(AuthOp.DatasetAttachmentReadPublic, datasetInstance); - } else if (group == AuthOp.DatasetAttachmentCreate) { + ability.can(Action.DatasetAttachmentReadAny, DatasetClass) || + ability.can(Action.DatasetAttachmentReadOwner, datasetInstance) || + ability.can(Action.DatasetAttachmentReadAccess, datasetInstance) || + ability.can(Action.DatasetAttachmentReadPublic, datasetInstance); + } else if (group == Action.DatasetAttachmentCreate) { canDoAction = - ability.can(AuthOp.DatasetAttachmentCreateAny, DatasetClass) || - ability.can(AuthOp.DatasetAttachmentCreateOwner, datasetInstance); - } else if (group == AuthOp.DatasetAttachmentUpdate) { + ability.can(Action.DatasetAttachmentCreateAny, DatasetClass) || + ability.can(Action.DatasetAttachmentCreateOwner, datasetInstance); + } else if (group == Action.DatasetAttachmentUpdate) { canDoAction = - ability.can(AuthOp.DatasetAttachmentUpdateAny, DatasetClass) || - ability.can(AuthOp.DatasetAttachmentUpdateOwner, datasetInstance); - } else if (group == AuthOp.DatasetAttachmentDelete) { + ability.can(Action.DatasetAttachmentUpdateAny, DatasetClass) || + ability.can(Action.DatasetAttachmentUpdateOwner, datasetInstance); + } else if (group == Action.DatasetAttachmentDelete) { canDoAction = - ability.can(AuthOp.DatasetAttachmentDeleteAny, DatasetClass) || - ability.can(AuthOp.DatasetAttachmentDeleteOwner, datasetInstance); - } else if (group == AuthOp.DatasetOrigdatablockRead) { + ability.can(Action.DatasetAttachmentDeleteAny, DatasetClass) || + ability.can(Action.DatasetAttachmentDeleteOwner, datasetInstance); + } else if (group == Action.DatasetOrigdatablockRead) { canDoAction = - ability.can(AuthOp.DatasetOrigdatablockReadAny, DatasetClass) || - ability.can(AuthOp.DatasetOrigdatablockReadOwner, datasetInstance) || - ability.can(AuthOp.DatasetOrigdatablockReadAccess, datasetInstance) || - ability.can(AuthOp.DatasetOrigdatablockReadPublic, datasetInstance); - } else if (group == AuthOp.DatasetOrigdatablockCreate) { + ability.can(Action.DatasetOrigdatablockReadAny, DatasetClass) || + ability.can(Action.DatasetOrigdatablockReadOwner, datasetInstance) || + ability.can(Action.DatasetOrigdatablockReadAccess, datasetInstance) || + ability.can(Action.DatasetOrigdatablockReadPublic, datasetInstance); + } else if (group == Action.DatasetOrigdatablockCreate) { canDoAction = - ability.can(AuthOp.DatasetOrigdatablockCreateAny, DatasetClass) || - ability.can(AuthOp.DatasetOrigdatablockCreateOwner, datasetInstance); - } else if (group == AuthOp.DatasetOrigdatablockUpdate) { + ability.can(Action.DatasetOrigdatablockCreateAny, DatasetClass) || + ability.can(Action.DatasetOrigdatablockCreateOwner, datasetInstance); + } else if (group == Action.DatasetOrigdatablockUpdate) { canDoAction = - ability.can(AuthOp.DatasetOrigdatablockUpdateAny, DatasetClass) || - ability.can(AuthOp.DatasetOrigdatablockUpdateOwner, datasetInstance); - } else if (group == AuthOp.DatasetOrigdatablockDelete) { + ability.can(Action.DatasetOrigdatablockUpdateAny, DatasetClass) || + ability.can(Action.DatasetOrigdatablockUpdateOwner, datasetInstance); + } else if (group == Action.DatasetOrigdatablockDelete) { canDoAction = - ability.can(AuthOp.DatasetOrigdatablockDeleteAny, DatasetClass) || - ability.can(AuthOp.DatasetOrigdatablockDeleteOwner, datasetInstance); - } else if (group == AuthOp.DatasetDatablockRead) { + ability.can(Action.DatasetOrigdatablockDeleteAny, DatasetClass) || + ability.can(Action.DatasetOrigdatablockDeleteOwner, datasetInstance); + } else if (group == Action.DatasetDatablockRead) { canDoAction = - ability.can(AuthOp.DatasetOrigdatablockReadAny, DatasetClass) || - ability.can(AuthOp.DatasetDatablockReadOwner, datasetInstance) || - ability.can(AuthOp.DatasetDatablockReadAccess, datasetInstance) || - ability.can(AuthOp.DatasetDatablockReadPublic, datasetInstance); - } else if (group == AuthOp.DatasetDatablockCreate) { + ability.can(Action.DatasetOrigdatablockReadAny, DatasetClass) || + ability.can(Action.DatasetDatablockReadOwner, datasetInstance) || + ability.can(Action.DatasetDatablockReadAccess, datasetInstance) || + ability.can(Action.DatasetDatablockReadPublic, datasetInstance); + } else if (group == Action.DatasetDatablockCreate) { canDoAction = - ability.can(AuthOp.DatasetDatablockCreateAny, DatasetClass) || - ability.can(AuthOp.DatasetDatablockCreateOwner, datasetInstance); - } else if (group == AuthOp.DatasetDatablockUpdate) { + ability.can(Action.DatasetDatablockCreateAny, DatasetClass) || + ability.can(Action.DatasetDatablockCreateOwner, datasetInstance); + } else if (group == Action.DatasetDatablockUpdate) { canDoAction = - ability.can(AuthOp.DatasetDatablockUpdateAny, DatasetClass) || - ability.can(AuthOp.DatasetDatablockUpdateOwner, datasetInstance); - } else if (group == AuthOp.DatasetDatablockDelete) { + ability.can(Action.DatasetDatablockUpdateAny, DatasetClass) || + ability.can(Action.DatasetDatablockUpdateOwner, datasetInstance); + } else if (group == Action.DatasetDatablockDelete) { canDoAction = - ability.can(AuthOp.DatasetDatablockDeleteAny, DatasetClass) || - ability.can(AuthOp.DatasetDatablockDeleteOwner, datasetInstance); - } else if (group == AuthOp.DatasetLogbookRead) { + ability.can(Action.DatasetDatablockDeleteAny, DatasetClass) || + ability.can(Action.DatasetDatablockDeleteOwner, datasetInstance); + } else if (group == Action.DatasetLogbookRead) { canDoAction = - ability.can(AuthOp.DatasetLogbookReadAny, DatasetClass) || - ability.can(AuthOp.DatasetLogbookReadOwner, datasetInstance); + ability.can(Action.DatasetLogbookReadAny, DatasetClass) || + ability.can(Action.DatasetLogbookReadOwner, datasetInstance); } if (!canDoAction) { throw new ForbiddenException("Unauthorized access"); @@ -290,12 +290,12 @@ export class DatasetsController { const datasetInstance = await this.generateDatasetInstanceForPermissions(dataset); - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); const canView = - ability.can(AuthOp.DatasetReadAny, DatasetClass) || - ability.can(AuthOp.DatasetReadOneOwner, datasetInstance) || - ability.can(AuthOp.DatasetReadOneAccess, datasetInstance) || - ability.can(AuthOp.DatasetReadOnePublic, datasetInstance); + ability.can(Action.DatasetReadAny, DatasetClass) || + ability.can(Action.DatasetReadOneOwner, datasetInstance) || + ability.can(Action.DatasetReadOneAccess, datasetInstance) || + ability.can(Action.DatasetReadOnePublic, datasetInstance); if (!canView) { throw new ForbiddenException("Unauthorized access"); @@ -355,12 +355,12 @@ export class DatasetsController { const datasetInstance = await this.generateDatasetInstanceForPermissions(dataset); // instantiate the casl matrix for the user - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); // check if he/she can create this dataset const canCreate = - ability.can(AuthOp.DatasetCreateAny, DatasetClass) || - ability.can(AuthOp.DatasetCreateOwnerNoPid, datasetInstance) || - ability.can(AuthOp.DatasetCreateOwnerWithPid, datasetInstance); + ability.can(Action.DatasetCreateAny, DatasetClass) || + ability.can(Action.DatasetCreateOwnerNoPid, datasetInstance) || + ability.can(Action.DatasetCreateOwnerWithPid, datasetInstance); if (!canCreate) { throw new ForbiddenException("Unauthorized to create this dataset"); @@ -389,8 +389,8 @@ export class DatasetsController { // POST /datasets @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetCreate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetCreate, DatasetClass), ) @UseInterceptors( new UTCTimeInterceptor(["creationTime"]), @@ -510,8 +510,8 @@ export class DatasetsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetCreate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetCreate, DatasetClass), ) @UseInterceptors( new UTCTimeInterceptor(["creationTime"]), @@ -568,8 +568,8 @@ export class DatasetsController { // GET /datasets @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @UseInterceptors(MainDatasetsPublicInterceptor) @Get() @@ -650,8 +650,8 @@ export class DatasetsController { // GET /datasets/fullquery @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @UseInterceptors(SubDatasetsPublicInterceptor, FullQueryInterceptor) @Get("/fullquery") @@ -691,16 +691,16 @@ export class DatasetsController { const user: JWTUser = request.user as JWTUser; const fields: IDatasetFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); if (!canViewAny && !fields.isPublished) { const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewOwner = ability.can( - AuthOp.DatasetReadManyOwner, + Action.DatasetReadManyOwner, DatasetClass, ); // const canViewPublic = ability.can( @@ -729,8 +729,8 @@ export class DatasetsController { // GET /fullfacets @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @UseInterceptors(SubDatasetsPublicInterceptor) @Get("/fullfacet") @@ -769,18 +769,18 @@ export class DatasetsController { const user: JWTUser = request.user as JWTUser; const fields: IDatasetFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); if (!canViewAny && !fields.isPublished) { // delete fields.isPublished; const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewOwner = ability.can( - AuthOp.DatasetReadManyOwner, + Action.DatasetReadManyOwner, DatasetClass, ); // const canViewPublic = ability.can( @@ -811,8 +811,8 @@ export class DatasetsController { // GET /datasets/metadataKeys @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @UseInterceptors(SubDatasetsPublicInterceptor) @Get("/metadataKeys") @@ -850,18 +850,18 @@ export class DatasetsController { const user: JWTUser = request.user as JWTUser; const fields: IDatasetFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); if (!canViewAny && !fields.isPublished) { // delete fields.isPublished; const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewOwner = ability.can( - AuthOp.DatasetReadManyOwner, + Action.DatasetReadManyOwner, DatasetClass, ); // const canViewPublic = ability.can( @@ -890,8 +890,8 @@ export class DatasetsController { // GET /datasets/findOne @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @Get("/findOne") @ApiOperation({ @@ -961,8 +961,8 @@ export class DatasetsController { // GET /datasets/count @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @Get("/count") @ApiOperation({ @@ -1001,8 +1001,8 @@ export class DatasetsController { // GET /datasets/:id //@UseGuards(PoliciesGuard) @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) @Get("/:pid") @ApiOperation({ @@ -1032,8 +1032,8 @@ export class DatasetsController { // PATCH /datasets/:id // body: modified fields @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetUpdate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetUpdate, DatasetClass), ) @UseInterceptors( new UTCTimeInterceptor(["creationTime"]), @@ -1098,11 +1098,11 @@ export class DatasetsController { // instantiate the casl matrix for the user const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); // check if he/she can create this dataset const canUpdate = - ability.can(AuthOp.DatasetUpdateAny, DatasetClass) || - ability.can(AuthOp.DatasetUpdateOwner, datasetInstance); + ability.can(Action.DatasetUpdateAny, DatasetClass) || + ability.can(Action.DatasetUpdateOwner, datasetInstance); if (!canUpdate) { throw new ForbiddenException("Unauthorized to update this dataset"); @@ -1113,8 +1113,8 @@ export class DatasetsController { // PUT /datasets/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetUpdate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetUpdate, DatasetClass), ) @UseInterceptors( new UTCTimeInterceptor(["creationTime"]), @@ -1177,11 +1177,11 @@ export class DatasetsController { // instantiate the casl matrix for the user const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); // check if he/she can create this dataset const canUpdate = - ability.can(AuthOp.DatasetUpdateAny, DatasetClass) || - ability.can(AuthOp.DatasetUpdateOwner, datasetInstance); + ability.can(Action.DatasetUpdateAny, DatasetClass) || + ability.can(Action.DatasetUpdateOwner, datasetInstance); if (!canUpdate) { throw new ForbiddenException("Unauthorized to update this dataset"); @@ -1195,8 +1195,8 @@ export class DatasetsController { // DELETE /datasets/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetDelete, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetDelete, DatasetClass), ) @Delete("/:pid") @ApiOperation({ @@ -1227,11 +1227,11 @@ export class DatasetsController { // instantiate the casl matrix for the user const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); // check if he/she can create this dataset const canUpdate = - ability.can(AuthOp.DatasetDeleteAny, DatasetClass) || - ability.can(AuthOp.DatasetDeleteOwner, datasetInstance); + ability.can(Action.DatasetDeleteAny, DatasetClass) || + ability.can(Action.DatasetDeleteOwner, datasetInstance); if (!canUpdate) { throw new ForbiddenException("Unauthorized to update this dataset"); @@ -1241,8 +1241,8 @@ export class DatasetsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetUpdate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetUpdate, DatasetClass), ) @Post("/:pid/appendToArrayField") @ApiOperation({ @@ -1278,7 +1278,7 @@ export class DatasetsController { @Query("data") data: string, ): Promise { const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.datasetInstanceAccess(user); const datasetToUpdate = await this.datasetsService.findOne({ where: { pid: pid }, }); @@ -1292,8 +1292,8 @@ export class DatasetsController { // check if he/she can create this dataset const canUpdate = - ability.can(AuthOp.DatasetDeleteAny, DatasetClass) || - ability.can(AuthOp.DatasetDeleteOwner, datasetInstance); + ability.can(Action.DatasetDeleteAny, DatasetClass) || + ability.can(Action.DatasetDeleteOwner, datasetInstance); if (!canUpdate) { throw new ForbiddenException("Unauthorized to update this dataset"); @@ -1312,8 +1312,8 @@ export class DatasetsController { // GET /datasets/:id/thumbnail @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), ) // @UseGuards(PoliciesGuard) @Get("/:pid/thumbnail") @@ -1339,7 +1339,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetRead, + Action.DatasetRead, ); const attachment = await this.attachmentsService.findOne( @@ -1356,8 +1356,8 @@ export class DatasetsController { // POST /datasets/:id/attachments @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetAttachmentCreate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetAttachmentCreate, DatasetClass), ) @HttpCode(HttpStatus.CREATED) @Post("/:pid/attachments") @@ -1390,7 +1390,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetAttachmentCreate, + Action.DatasetAttachmentCreate, ); if (dataset) { @@ -1406,8 +1406,8 @@ export class DatasetsController { // GET /datasets/:id/attachments @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetAttachmentRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetAttachmentRead, DatasetClass), ) @Get("/:pid/attachments") @ApiOperation({ @@ -1435,7 +1435,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetAttachmentRead, + Action.DatasetAttachmentRead, ); return this.attachmentsService.findAll({ datasetId: pid }); @@ -1443,8 +1443,8 @@ export class DatasetsController { // PATCH /datasets/:id/attachments/:fk @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetAttachmentUpdate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetAttachmentUpdate, DatasetClass), ) @Put("/:pid/attachments/:aid") @ApiOperation({ @@ -1479,7 +1479,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetAttachmentUpdate, + Action.DatasetAttachmentUpdate, ); return this.attachmentsService.findOneAndUpdate( @@ -1490,8 +1490,8 @@ export class DatasetsController { // DELETE /datasets/:pid/attachments/:aid @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetAttachmentDelete, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetAttachmentDelete, DatasetClass), ) @Delete("/:pid/attachments/:aid") @ApiOperation({ @@ -1523,7 +1523,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetAttachmentDelete, + Action.DatasetAttachmentDelete, ); return this.attachmentsService.findOneAndDelete({ @@ -1534,8 +1534,8 @@ export class DatasetsController { // POST /datasets/:id/origdatablocks @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => { - return ability.can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); + @CheckPolicies("datasets", (ability: AppAbility) => { + return ability.can(Action.DatasetOrigdatablockCreate, DatasetClass); }) @UseInterceptors( new MultiUTCTimeInterceptor("dataFileList", [ @@ -1571,7 +1571,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetOrigdatablockCreate, + Action.DatasetOrigdatablockCreate, ); if (dataset) { @@ -1600,8 +1600,8 @@ export class DatasetsController { // POST /datasets/:id/origdatablocks/isValid @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => { - return ability.can(AuthOp.DatasetOrigdatablockCreate, DatasetClass); + @CheckPolicies("datasets", (ability: AppAbility) => { + return ability.can(Action.DatasetOrigdatablockCreate, DatasetClass); }) @HttpCode(HttpStatus.OK) @Post("/:pid/origdatablocks/isValid") @@ -1633,7 +1633,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetOrigdatablockCreate, + Action.DatasetOrigdatablockCreate, ); const dtoTestOrigDatablock = plainToInstance( @@ -1649,8 +1649,8 @@ export class DatasetsController { // GET /datasets/:id/origdatablocks @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => { - return ability.can(AuthOp.DatasetOrigdatablockRead, DatasetClass); + @CheckPolicies("datasets", (ability: AppAbility) => { + return ability.can(Action.DatasetOrigdatablockRead, DatasetClass); }) @Get("/:pid/origdatablocks") @ApiOperation({ @@ -1678,7 +1678,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetOrigdatablockRead, + Action.DatasetOrigdatablockRead, ); return this.origDatablocksService.findAll({ where: { datasetId: pid } }); @@ -1686,8 +1686,8 @@ export class DatasetsController { // PATCH /datasets/:id/origdatablocks/:fk @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => { - return ability.can(AuthOp.DatasetOrigdatablockUpdate, DatasetClass); + @CheckPolicies("datasets", (ability: AppAbility) => { + return ability.can(Action.DatasetOrigdatablockUpdate, DatasetClass); }) @UseInterceptors( new MultiUTCTimeInterceptor("dataFileList", [ @@ -1729,7 +1729,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetOrigdatablockUpdate, + Action.DatasetOrigdatablockUpdate, ); const origDatablockBeforeUpdate = await this.origDatablocksService.findOne({ @@ -1757,8 +1757,8 @@ export class DatasetsController { // DELETE /datasets/:id/origdatablocks/:fk @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetOrigdatablockDelete, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetOrigdatablockDelete, DatasetClass), ) @Delete("/:pid/origdatablocks/:oid") @ApiOperation({ @@ -1790,7 +1790,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetOrigdatablockDelete, + Action.DatasetOrigdatablockDelete, ); if (dataset) { @@ -1819,8 +1819,8 @@ export class DatasetsController { // POST /datasets/:id/datablocks @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetDatablockCreate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetDatablockCreate, DatasetClass), ) @UseInterceptors( new MultiUTCTimeInterceptor("dataFileList", ["time"]), @@ -1854,7 +1854,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetDatablockCreate, + Action.DatasetDatablockCreate, ); if (dataset) { @@ -1880,8 +1880,8 @@ export class DatasetsController { // GET /datasets/:id/datablocks @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetDatablockRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetDatablockRead, DatasetClass), ) @Get("/:pid/datablocks") @ApiOperation({ @@ -1909,7 +1909,7 @@ export class DatasetsController { await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetDatablockRead, + Action.DatasetDatablockRead, ); return this.datablocksService.findAll({ datasetId: pid }); @@ -1917,8 +1917,8 @@ export class DatasetsController { // PATCH /datasets/:id/datablocks/:fk @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetDatablockUpdate, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetDatablockUpdate, DatasetClass), ) @UseInterceptors( new MultiUTCTimeInterceptor("dataFileList", ["time"]), @@ -1957,7 +1957,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetDatablockUpdate, + Action.DatasetDatablockUpdate, ); const datablockBeforeUpdate = await this.datablocksService.findOne({ @@ -1987,8 +1987,8 @@ export class DatasetsController { // DELETE /datasets/:id/datablocks/:fk @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetDatablockDelete, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetDatablockDelete, DatasetClass), ) @Delete("/:pid/datablocks/:did") @ApiOperation({ @@ -2020,7 +2020,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetDatablockDelete, + Action.DatasetDatablockDelete, ); if (dataset) { @@ -2056,8 +2056,8 @@ export class DatasetsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.DatasetLogbookRead, DatasetClass), + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetLogbookRead, DatasetClass), ) @Get("/:pid/logbook") @ApiOperation({ @@ -2084,7 +2084,7 @@ export class DatasetsController { const dataset = await this.checkPermissionsForDatasetExtended( request, pid, - AuthOp.DatasetLogbookRead, + Action.DatasetLogbookRead, ); const proposalId = dataset?.proposalId; diff --git a/src/elastic-search/elastic-search.controller.ts b/src/elastic-search/elastic-search.controller.ts index 342f4ca52..de2498da6 100644 --- a/src/elastic-search/elastic-search.controller.ts +++ b/src/elastic-search/elastic-search.controller.ts @@ -10,7 +10,7 @@ import { UseGuards, } from "@nestjs/common"; import { ApiTags, ApiBearerAuth, ApiBody, ApiResponse } from "@nestjs/swagger"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { AppAbility } from "src/casl/casl-ability.factory"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; @@ -37,8 +37,8 @@ export class ElasticSearchServiceController { ) {} @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.CREATED) @ApiResponse({ @@ -53,8 +53,8 @@ export class ElasticSearchServiceController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.OK) @ApiResponse({ @@ -70,8 +70,8 @@ export class ElasticSearchServiceController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.OK) @UseInterceptors(SubDatasetsPublicInterceptor) @@ -88,8 +88,8 @@ export class ElasticSearchServiceController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.OK) @ApiResponse({ @@ -104,8 +104,8 @@ export class ElasticSearchServiceController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.OK) @ApiResponse({ @@ -120,8 +120,8 @@ export class ElasticSearchServiceController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Manage, ElasticSearchActions), + @CheckPolicies("elastic-search", (ability: AppAbility) => + ability.can(Action.Manage, ElasticSearchActions), ) @HttpCode(HttpStatus.OK) @ApiResponse({ diff --git a/src/instruments/instruments.controller.ts b/src/instruments/instruments.controller.ts index 788fee9a1..7ee9a6216 100644 --- a/src/instruments/instruments.controller.ts +++ b/src/instruments/instruments.controller.ts @@ -25,7 +25,7 @@ import { import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { Instrument, InstrumentDocument } from "./schemas/instrument.schema"; import { FormatPhysicalQuantitiesInterceptor } from "src/common/interceptors/format-physical-quantities.interceptor"; import { IFilters } from "src/common/interfaces/common.interface"; @@ -42,8 +42,8 @@ export class InstrumentsController { constructor(private readonly instrumentsService: InstrumentsService) {} @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentCreate, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentCreate, Instrument), ) @UseInterceptors( new FormatPhysicalQuantitiesInterceptor("customMetadata"), @@ -69,8 +69,8 @@ export class InstrumentsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentRead, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentRead, Instrument), ) @Get() @ApiQuery({ @@ -87,8 +87,8 @@ export class InstrumentsController { // GET /instrument/findOne @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentRead, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentRead, Instrument), ) @Get("/findOne") @ApiOperation({ @@ -117,8 +117,8 @@ export class InstrumentsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentRead, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentRead, Instrument), ) @Get(":id") async findById(@Param("id") pid: string): Promise { @@ -126,8 +126,8 @@ export class InstrumentsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentUpdate, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentUpdate, Instrument), ) @UseInterceptors( new FormatPhysicalQuantitiesInterceptor("customMetadata"), @@ -152,8 +152,8 @@ export class InstrumentsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.InstrumentDelete, Instrument), + @CheckPolicies("instruments", (ability: AppAbility) => + ability.can(Action.InstrumentDelete, Instrument), ) @Delete(":id") async remove(@Param("id") id: string): Promise { diff --git a/src/jobs/config/jobconfig.ts b/src/jobs/config/jobconfig.ts index 0d31e1d5d..72ee19cc1 100644 --- a/src/jobs/config/jobconfig.ts +++ b/src/jobs/config/jobconfig.ts @@ -18,7 +18,7 @@ import { JobClass } from "../schemas/job.schema"; import { CreateJobDto } from "../dto/create-job.dto"; import { StatusUpdateJobDto } from "../dto/status-update-job.dto"; import { JobsConfigSchema } from "../types/jobs-config-schema.enum"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { CreateJobAuth, JobsAuth } from "../types/jobs-auth.enum"; import Ajv from "ajv"; import { JobConfigSchema } from "./jobConfig.schema"; @@ -49,6 +49,7 @@ export class JobConfig { * @param data JSON * @returns */ + static parse( jobData: Record, configVersion: string, @@ -60,21 +61,21 @@ export class JobConfig { throw new Error(`Invalid job type`); } const type = jobData[JobsConfigSchema.JobType] as string; - if (!(AuthOp.Create in jobData)) { - throw new Error(`No ${AuthOp.Create} configured for job type "${type}"`); + if (!(Action.Create in jobData)) { + throw new Error(`No ${Action.Create} configured for job type "${type}"`); } - if (!(AuthOp.StatusUpdate in jobData)) { + if (!(Action.StatusUpdate in jobData)) { throw new Error( - `No ${AuthOp.StatusUpdate} configured for job type "${type}"`, + `No ${Action.StatusUpdate} configured for job type "${type}"`, ); } const create = JobOperation.parse( createActions, - jobData[AuthOp.Create] as Record, + jobData[Action.Create] as Record, ); const statusUpdate = JobOperation.parse( statusUpdateActions, - jobData[AuthOp.StatusUpdate] as Record, + jobData[Action.StatusUpdate] as Record, ); return new JobConfig(type, configVersion, create, statusUpdate); } @@ -244,7 +245,6 @@ export function loadJobConfig(filePath: string): JobConfig[] { } else { console.log("Invalid Schema", JSON.stringify(validate.errors, null, 2)); } - jobConfig = data.jobs.map((jobData: Record) => JobConfig.parse(jobData, data.configVersion), ); diff --git a/src/jobs/jobs.controller.ts b/src/jobs/jobs.controller.ts index fb97fbca3..29dfb63cf 100644 --- a/src/jobs/jobs.controller.ts +++ b/src/jobs/jobs.controller.ts @@ -22,7 +22,7 @@ import { StatusUpdateJobDto } from "./dto/status-update-job.dto"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { CreateJobAuth } from "src/jobs/types/jobs-auth.enum"; import { JobClass, JobDocument } from "./schemas/job.schema"; import { @@ -385,14 +385,14 @@ export class JobsController { // If other fields are needed can be added later. const jobInstance = new JobClass(); const jobConfiguration = this.getJobTypeConfiguration(jobCreateDto.type); - jobInstance._id = ""; jobInstance.accessGroups = []; jobInstance.type = jobCreateDto.type; jobInstance.contactEmail = jobCreateDto.contactEmail; jobInstance.jobParams = jobCreateDto.jobParams; jobInstance.datasetsValidation = false; - jobInstance.configuration = jobConfiguration; + jobInstance.configVersion = + jobConfiguration[JobsConfigSchema.ConfigVersion]; jobInstance.statusCode = "Initializing"; jobInstance.statusMessage = "Building and validating job, verifying authorization"; @@ -471,7 +471,6 @@ export class JobsController { ); } // verify that the user meet the requested permissions on the datasets listed - // const datasetIds = await this.checkDatasetIds(jobCreateDto.jobParams); // build the condition interface datasetsWhere { where: { @@ -519,12 +518,15 @@ export class JobsController { } // instantiate the casl matrix for the user - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.jobsInstanceAccess( + user, + jobConfiguration, + ); // check if the user can create this job const canCreate = - ability.can(AuthOp.JobCreateAny, JobClass) || - ability.can(AuthOp.JobCreateOwner, jobInstance) || - ability.can(AuthOp.JobCreateConfiguration, jobInstance); + ability.can(Action.JobCreateAny, JobClass) || + ability.can(Action.JobCreateOwner, jobInstance) || + ability.can(Action.JobCreateConfiguration, jobInstance); if (!canCreate) { throw new ForbiddenException("Unauthorized to create this job."); @@ -567,10 +569,10 @@ export class JobsController { const jobConfig = this.getJobTypeConfiguration(jobInstance.type); // TODO - what shall we do when configVersion does not match? - if (jobConfig.configVersion !== jobInstance.configuration.configVersion) { + if (jobConfig.configVersion !== jobInstance.configVersion) { Logger.log( ` - Job was created with configVersion ${jobInstance.configuration.configVersion}. + Job was created with configVersion ${jobInstance.configVersion}. Current configVersion is ${jobConfig.configVersion}. `, "JobStatusUpdate", @@ -587,8 +589,8 @@ export class JobsController { * Create job */ @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.JobCreate, JobClass), + @CheckPolicies("jobs", (ability: AppAbility) => + ability.can(Action.JobCreate, JobClass), ) // @UseInterceptors(JobCreateInterceptor) @Post() @@ -641,8 +643,8 @@ export class JobsController { * Update job status */ @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.JobStatusUpdate, JobClass), + @CheckPolicies("jobs", (ability: AppAbility) => + ability.can(Action.JobStatusUpdate, JobClass), ) @Patch(":id") @ApiOperation({ @@ -678,20 +680,18 @@ export class JobsController { } const currentJobInstance = await this.generateJobInstanceForPermissions(currentJob); - currentJobInstance.configuration = this.getJobTypeConfiguration( - currentJobInstance.type, - ); - - const ability = this.caslAbilityFactory.createForUser( + const jobConfiguration = this.getJobTypeConfiguration(currentJob.type); + const ability = this.caslAbilityFactory.jobsInstanceAccess( request.user as JWTUser, + jobConfiguration, ); - // check if he/she can create this dataset + // check if the user can update this job const canUpdateStatus = - ability.can(AuthOp.JobStatusUpdateAny, JobClass) || - ability.can(AuthOp.JobStatusUpdateOwner, currentJobInstance) || - ability.can(AuthOp.JobStatusUpdateConfiguration, currentJobInstance); + ability.can(Action.JobStatusUpdateAny, JobClass) || + ability.can(Action.JobStatusUpdateOwner, currentJobInstance) || + ability.can(Action.JobStatusUpdateConfiguration, currentJobInstance); if (!canUpdateStatus) { - throw new ForbiddenException("Unauthorized to update this dataset"); + throw new ForbiddenException("Unauthorized to update this job."); } // Update job in database @@ -703,15 +703,6 @@ export class JobsController { if (updatedJob !== null) { await this.performJobStatusUpdateAction(updatedJob); } - - // Emit update event - // MN: not needed - // if (updatedJob) { - // this.eventEmitter.emit("jobUpdated", { - // instance: updatedJob, - // hookState: { oldData: [updatedJob] }, - // }); - // } return updatedJob; } @@ -719,7 +710,9 @@ export class JobsController { * Get job by id */ @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.JobRead, JobClass)) + @CheckPolicies("jobs", (ability: AppAbility) => + ability.can(Action.JobRead, JobClass), + ) @Get(":id") @ApiOperation({ summary: "It returns the requested job.", @@ -746,14 +739,17 @@ export class JobsController { } const currentJobInstance = await this.generateJobInstanceForPermissions(currentJob); - const ability = this.caslAbilityFactory.createForUser( + + const jobConfiguration = this.getJobTypeConfiguration(currentJob.type); + const ability = this.caslAbilityFactory.jobsInstanceAccess( request.user as JWTUser, + jobConfiguration, ); - const canCreate = - ability.can(AuthOp.JobReadAny, JobClass) || - ability.can(AuthOp.JobReadAccess, currentJobInstance); - if (!canCreate) { - throw new ForbiddenException("Unauthorized to update this dataset"); + const canRead = + ability.can(Action.JobReadAny, JobClass) || + ability.can(Action.JobReadAccess, currentJobInstance); + if (!canRead) { + throw new ForbiddenException("Unauthorized to get this job."); } return currentJob; } @@ -762,7 +758,9 @@ export class JobsController { * Get jobs */ @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.JobRead, JobClass)) + @CheckPolicies("jobs", (ability: AppAbility) => + ability.can(Action.JobRead, JobClass), + ) @Get() @ApiOperation({ summary: "It returns a list of jobs.", @@ -799,25 +797,29 @@ export class JobsController { throw { message: "Invalid filter syntax." }; } // for each job run a casl JobReadOwner on a jobInstance - const datasetsFound = await this.jobsService.findAll(parsedFilter); - const datasetsAccessible: JobClass[] = []; - const ability = this.caslAbilityFactory.createForUser( - request.user as JWTUser, - ); + const jobsFound = await this.jobsService.findAll(parsedFilter); + const jobsAccessible: JobClass[] = []; - for (const i in datasetsFound) { - // check if he/she can create this dataset + for (const i in jobsFound) { + const jobConfiguration = this.getJobTypeConfiguration( + jobsFound[i].type, + ); + const ability = this.caslAbilityFactory.jobsInstanceAccess( + request.user as JWTUser, + jobConfiguration, + ); + // check if the user can get this job const jobInstance = await this.generateJobInstanceForPermissions( - datasetsFound[i], + jobsFound[i], ); const canCreate = - ability.can(AuthOp.JobReadAny, JobClass) || - ability.can(AuthOp.JobReadAccess, jobInstance); + ability.can(Action.JobReadAny, JobClass) || + ability.can(Action.JobReadAccess, jobInstance); if (canCreate) { - datasetsAccessible.push(datasetsFound[i]); + jobsAccessible.push(jobsFound[i]); } } - return datasetsAccessible; + return jobsAccessible; } catch (e) { throw new HttpException( { @@ -833,10 +835,8 @@ export class JobsController { * Delete a job */ @UseGuards(PoliciesGuard) - @CheckPolicies( - (ability: AppAbility) => - ability.can(AuthOp.JobDelete, JobClass) && - ability.can(AuthOp.JobDeleteAny, JobClass), + @CheckPolicies("jobs", (ability: AppAbility) => + ability.can(Action.JobDelete, JobClass), ) @Delete(":id") @ApiOperation({ diff --git a/src/jobs/schemas/job.schema.ts b/src/jobs/schemas/job.schema.ts index cf0359726..fe8e51003 100644 --- a/src/jobs/schemas/job.schema.ts +++ b/src/jobs/schemas/job.schema.ts @@ -3,7 +3,6 @@ import { ApiProperty } from "@nestjs/swagger"; import { Document } from "mongoose"; import { v4 as uuidv4 } from "uuid"; import { OwnableClass } from "src/common/schemas/ownable.schema"; -import { JobConfig } from "../config/jobconfig"; export type JobDocument = JobClass & Document; @@ -123,14 +122,14 @@ export class JobClass extends OwnableClass { @ApiProperty({ type: Object, - description: "Configuration that was used to create this job.", + description: "Configuration version that was used to create this job.", required: true, }) @Prop({ type: Object, required: true, }) - configuration: JobConfig; + configVersion: string; } export const JobSchema = SchemaFactory.createForClass(JobClass); diff --git a/src/logbooks/logbooks.controller.ts b/src/logbooks/logbooks.controller.ts index 92c2a7a5b..628997c7c 100644 --- a/src/logbooks/logbooks.controller.ts +++ b/src/logbooks/logbooks.controller.ts @@ -11,7 +11,7 @@ import { ApiBearerAuth, ApiTags } from "@nestjs/swagger"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { Logbook } from "./schemas/logbook.schema"; import { UsersLogbooksInterceptor } from "./interceptors/users-logbooks.interceptor"; @@ -22,7 +22,9 @@ export class LogbooksController { constructor(private readonly logbooksService: LogbooksService) {} @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Read, Logbook)) + @CheckPolicies("logbooks", (ability: AppAbility) => + ability.can(Action.Read, Logbook), + ) @UseInterceptors(UsersLogbooksInterceptor) @Get() findAll() { @@ -30,7 +32,9 @@ export class LogbooksController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Read, Logbook)) + @CheckPolicies("logbooks", (ability: AppAbility) => + ability.can(Action.Read, Logbook), + ) @UseInterceptors(UsersLogbooksInterceptor) @Get("/:name") async findByName( diff --git a/src/origdatablocks/origdatablocks.controller.ts b/src/origdatablocks/origdatablocks.controller.ts index f3aa61b3e..4888c2baa 100644 --- a/src/origdatablocks/origdatablocks.controller.ts +++ b/src/origdatablocks/origdatablocks.controller.ts @@ -31,7 +31,7 @@ import { import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { OrigDatablock, OrigDatablockDocument, @@ -61,7 +61,7 @@ export class OrigDatablocksController { async checkPermissionsForOrigDatablock( request: Request, id: string, - group: AuthOp, + group: Action, ) { const origDatablock = await this.origDatablocksService.findOne({ _id: id }); if (!origDatablock) { @@ -116,7 +116,7 @@ export class OrigDatablocksController { async checkPermissionsForOrigDatablockExtended( request: Request, id: string, - group: AuthOp, + group: Action, ) { const dataset = await this.datasetsService.findOne({ where: { pid: id } }); const user: JWTUser = request.user as JWTUser; @@ -128,24 +128,24 @@ export class OrigDatablocksController { const origDatablockInstance = await this.generateOrigDatablockInstanceInstanceForPermissions(dataset); - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); let canDoAction = false; - if (group == AuthOp.OrigdatablockCreate) { + if (group == Action.OrigdatablockCreate) { canDoAction = - ability.can(AuthOp.OrigdatablockCreateAny, origDatablockInstance) || - ability.can(AuthOp.OrigdatablockCreateOwner, origDatablockInstance); - } else if (group == AuthOp.OrigdatablockRead) { + ability.can(Action.OrigdatablockCreateAny, origDatablockInstance) || + ability.can(Action.OrigdatablockCreateOwner, origDatablockInstance); + } else if (group == Action.OrigdatablockRead) { canDoAction = - ability.can(AuthOp.OrigdatablockReadAny, origDatablockInstance) || - ability.can(AuthOp.OrigdatablockReadOnePublic, origDatablockInstance) || - ability.can(AuthOp.OrigdatablockReadOneAccess, origDatablockInstance) || - ability.can(AuthOp.OrigdatablockReadOneOwner, origDatablockInstance); - } else if (group == AuthOp.OrigdatablockUpdate) { + ability.can(Action.OrigdatablockReadAny, origDatablockInstance) || + ability.can(Action.OrigdatablockReadOnePublic, origDatablockInstance) || + ability.can(Action.OrigdatablockReadOneAccess, origDatablockInstance) || + ability.can(Action.OrigdatablockReadOneOwner, origDatablockInstance); + } else if (group == Action.OrigdatablockUpdate) { canDoAction = - ability.can(AuthOp.OrigdatablockUpdateAny, origDatablockInstance) || - ability.can(AuthOp.OrigdatablockUpdateOwner, origDatablockInstance); + ability.can(Action.OrigdatablockUpdateAny, origDatablockInstance) || + ability.can(Action.OrigdatablockUpdateOwner, origDatablockInstance); } if (!canDoAction) { @@ -157,8 +157,8 @@ export class OrigDatablocksController { // POST /origdatablocks @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockCreate, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockCreate, OrigDatablock), ) @HttpCode(HttpStatus.CREATED) @Post() @@ -185,7 +185,7 @@ export class OrigDatablocksController { const dataset = await this.checkPermissionsForOrigDatablockExtended( request, createOrigDatablockDto.datasetId, - AuthOp.OrigdatablockCreate, + Action.OrigdatablockCreate, ); if (dataset) { @@ -226,8 +226,8 @@ export class OrigDatablocksController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockCreate, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockCreate, OrigDatablock), ) @HttpCode(HttpStatus.OK) @Post("/isValid") @@ -255,7 +255,7 @@ export class OrigDatablocksController { await this.checkPermissionsForOrigDatablockExtended( request, (createOrigDatablock as CreateOrigDatablockDto).datasetId, - AuthOp.OrigdatablockCreate, + Action.OrigdatablockCreate, ); const dtoTestOrigDatablock = plainToInstance( CreateOrigDatablockDto, @@ -271,8 +271,8 @@ export class OrigDatablocksController { // GET /origdatablock @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get() @ApiOperation({ @@ -302,16 +302,16 @@ export class OrigDatablocksController { const user: JWTUser = request.user as JWTUser; const parsedFilters: IFilters = JSON.parse(filter ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.OrigdatablockReadAny, OrigDatablock); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); + const canViewAny = ability.can(Action.OrigdatablockReadAny, OrigDatablock); if (!canViewAny) { parsedFilters.where = parsedFilters.where ?? {}; const canViewAccess = ability.can( - AuthOp.OrigdatablockReadManyAccess, + Action.OrigdatablockReadManyAccess, OrigDatablock, ); const canViewOwner = ability.can( - AuthOp.OrigdatablockReadManyOwner, + Action.OrigdatablockReadManyOwner, OrigDatablock, ); @@ -329,8 +329,8 @@ export class OrigDatablocksController { // GET /origdatablocks/fullquery @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get("/fullquery") @ApiQuery({ @@ -355,16 +355,16 @@ export class OrigDatablocksController { const user: JWTUser = request.user as JWTUser; const fields: IOrigDatablockFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.OrigdatablockReadAny, OrigDatablock); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); + const canViewAny = ability.can(Action.OrigdatablockReadAny, OrigDatablock); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.OrigdatablockReadManyAccess, + Action.OrigdatablockReadManyAccess, OrigDatablock, ); const canViewOwner = ability.can( - AuthOp.OrigdatablockReadManyOwner, + Action.OrigdatablockReadManyOwner, OrigDatablock, ); @@ -393,8 +393,8 @@ export class OrigDatablocksController { // GET /origdatablocks/fullquery/files @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get("/fullquery/files") @ApiQuery({ @@ -418,15 +418,15 @@ export class OrigDatablocksController { ): Promise { const user: JWTUser = request.user as JWTUser; const fields: IOrigDatablockFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.OrigdatablockReadAny, OrigDatablock); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); + const canViewAny = ability.can(Action.OrigdatablockReadAny, OrigDatablock); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.OrigdatablockReadManyAccess, + Action.OrigdatablockReadManyAccess, OrigDatablock, ); const canViewOwner = ability.can( - AuthOp.OrigdatablockReadManyOwner, + Action.OrigdatablockReadManyOwner, OrigDatablock, ); @@ -448,8 +448,8 @@ export class OrigDatablocksController { // GET /origdatablocks/fullfacet @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get("/fullfacet") async fullfacet( @@ -459,15 +459,15 @@ export class OrigDatablocksController { const user: JWTUser = request.user as JWTUser; const fields: IOrigDatablockFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.OrigdatablockReadAny, OrigDatablock); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); + const canViewAny = ability.can(Action.OrigdatablockReadAny, OrigDatablock); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.OrigdatablockReadManyAccess, + Action.OrigdatablockReadManyAccess, OrigDatablock, ); const canViewOwner = ability.can( - AuthOp.OrigdatablockReadManyOwner, + Action.OrigdatablockReadManyOwner, OrigDatablock, ); @@ -489,8 +489,8 @@ export class OrigDatablocksController { // GET /origdatablocks/fullfacet/files @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get("/fullfacet/files") async fullfacetFiles( @@ -500,15 +500,15 @@ export class OrigDatablocksController { const user: JWTUser = request.user as JWTUser; const fields: IOrigDatablockFields = JSON.parse(filters.fields ?? "{}"); - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.OrigdatablockReadAny, OrigDatablock); + const ability = this.caslAbilityFactory.origDatablockInstanceAccess(user); + const canViewAny = ability.can(Action.OrigdatablockReadAny, OrigDatablock); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.OrigdatablockReadManyAccess, + Action.OrigdatablockReadManyAccess, OrigDatablock, ); const canViewOwner = ability.can( - AuthOp.OrigdatablockReadManyOwner, + Action.OrigdatablockReadManyOwner, OrigDatablock, ); @@ -534,8 +534,8 @@ export class OrigDatablocksController { // GET /origdatablocks/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockRead, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockRead, OrigDatablock), ) @Get("/:id") @ApiOperation({ @@ -560,7 +560,7 @@ export class OrigDatablocksController { const origdatablock = await this.checkPermissionsForOrigDatablock( request, id, - AuthOp.OrigdatablockRead, + Action.OrigdatablockRead, ); return origdatablock; @@ -568,8 +568,8 @@ export class OrigDatablocksController { // PATCH /origdatablocks/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockUpdate, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockUpdate, OrigDatablock), ) @Patch("/:id") @ApiOperation({ @@ -600,7 +600,7 @@ export class OrigDatablocksController { await this.checkPermissionsForOrigDatablock( request, id, - AuthOp.OrigdatablockUpdate, + Action.OrigdatablockUpdate, ); const origdatablock = (await this.origDatablocksService.update( @@ -615,8 +615,8 @@ export class OrigDatablocksController { // DELETE /origdatablocks/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.OrigdatablockDelete, OrigDatablock), + @CheckPolicies("origdatablocks", (ability: AppAbility) => + ability.can(Action.OrigdatablockDelete, OrigDatablock), ) @Delete("/:id") @ApiOperation({ diff --git a/src/policies/policies.controller.ts b/src/policies/policies.controller.ts index 5b2a523f0..ba8952bcf 100644 --- a/src/policies/policies.controller.ts +++ b/src/policies/policies.controller.ts @@ -24,7 +24,7 @@ import { ApiBearerAuth, ApiQuery, ApiTags } from "@nestjs/swagger"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { Policy, PolicyDocument } from "./schemas/policy.schema"; import { FilterQuery } from "mongoose"; import { IPolicyFilter } from "./interfaces/policy-filters.interface"; @@ -84,9 +84,9 @@ export class PoliciesController { const user: JWTUser = request.user as JWTUser; if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.ListAll, Policy); - const canViewTheirOwn = ability.can(AuthOp.ListOwn, Policy); + const ability = this.caslAbilityFactory.policyEndpointAccess(user); + const canViewAll = ability.can(Action.ListAll, Policy); + const canViewTheirOwn = ability.can(Action.ListOwn, Policy); if (!canViewAll && canViewTheirOwn) { if (!mergedFilters.where) { mergedFilters.where = {}; @@ -102,14 +102,18 @@ export class PoliciesController { return mergedFilters; } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Create, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Create, Policy), + ) @Post() async create(@Body() createPolicyDto: CreatePolicyDto): Promise { return this.policiesService.create(createPolicyDto); } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Read, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Read, Policy), + ) @Get() @ApiQuery({ name: "filter", @@ -133,7 +137,9 @@ export class PoliciesController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Read, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Read, Policy), + ) @Get("/count") async count(@Query("where") where?: string): Promise<{ count: number }> { const parsedWhere: FilterQuery = JSON.parse(where ?? "{}"); @@ -141,7 +147,9 @@ export class PoliciesController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Update, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Update, Policy), + ) @UseInterceptors(HistoryInterceptor) @HttpCode(HttpStatus.OK) @Post("/updateWhere") @@ -153,14 +161,18 @@ export class PoliciesController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Read, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Read, Policy), + ) @Get(":id") async findOne(@Param("id") id: string): Promise { return this.policiesService.findOne({ _id: id }); } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Update, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Update, Policy), + ) @Patch(":id") async update( @Param("id") id: string, @@ -170,7 +182,9 @@ export class PoliciesController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Delete, Policy)) + @CheckPolicies("policies", (ability: AppAbility) => + ability.can(Action.Delete, Policy), + ) @Delete(":id") async remove(@Param("id") id: string): Promise { return this.policiesService.remove({ _id: id }); diff --git a/src/proposals/proposals.controller.ts b/src/proposals/proposals.controller.ts index 80b3ebb2b..ca569e446 100644 --- a/src/proposals/proposals.controller.ts +++ b/src/proposals/proposals.controller.ts @@ -33,10 +33,9 @@ import { ApiTags, } from "@nestjs/swagger"; import { PoliciesGuard } from "src/casl/guards/policies.guard"; -import { AuthenticatedPoliciesGuard } from "../casl/guards/auth-check.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { ProposalClass, ProposalDocument } from "./schemas/proposal.schema"; import { AttachmentsService } from "src/attachments/attachments.service"; import { Attachment } from "src/attachments/schemas/attachment.schema"; @@ -91,7 +90,7 @@ export class ProposalsController { } private async permissionChecker( - group: AuthOp, + group: Action, proposal: ProposalClass | CreateProposalDto, request: Request, ) { @@ -100,59 +99,59 @@ export class ProposalsController { ); const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.proposalsInstanceAccess(user); try { switch (group) { - case AuthOp.ProposalsCreate: + case Action.ProposalsCreate: return ( - ability.can(AuthOp.ProposalsCreateAny, ProposalClass) || - ability.can(AuthOp.ProposalsCreateOwner, proposalInstance) + ability.can(Action.ProposalsCreateAny, ProposalClass) || + ability.can(Action.ProposalsCreateOwner, proposalInstance) ); - case AuthOp.ProposalsRead: + case Action.ProposalsRead: return ( - ability.can(AuthOp.ProposalsReadAny, ProposalClass) || - ability.can(AuthOp.ProposalsReadOneOwner, proposalInstance) || - ability.can(AuthOp.ProposalsReadOneAccess, proposalInstance) || - ability.can(AuthOp.ProposalsReadOnePublic, proposalInstance) + ability.can(Action.ProposalsReadAny, ProposalClass) || + ability.can(Action.ProposalsReadOneOwner, proposalInstance) || + ability.can(Action.ProposalsReadOneAccess, proposalInstance) || + ability.can(Action.ProposalsReadOnePublic, proposalInstance) ); - case AuthOp.ProposalsUpdate: + case Action.ProposalsUpdate: return ( - ability.can(AuthOp.ProposalsUpdateAny, ProposalClass) || - ability.can(AuthOp.ProposalsUpdateOwner, proposalInstance) + ability.can(Action.ProposalsUpdateAny, ProposalClass) || + ability.can(Action.ProposalsUpdateOwner, proposalInstance) ); - case AuthOp.ProposalsDelete: + case Action.ProposalsDelete: return ( - ability.can(AuthOp.ProposalsDeleteAny, ProposalClass) || - ability.can(AuthOp.ProposalsDeleteOwner, proposalInstance) + ability.can(Action.ProposalsDeleteAny, ProposalClass) || + ability.can(Action.ProposalsDeleteOwner, proposalInstance) ); - case AuthOp.ProposalsAttachmentCreate: + case Action.ProposalsAttachmentCreate: return ( - ability.can(AuthOp.ProposalsAttachmentCreateAny, ProposalClass) || - ability.can(AuthOp.ProposalsAttachmentCreateOwner, proposalInstance) + ability.can(Action.ProposalsAttachmentCreateAny, ProposalClass) || + ability.can(Action.ProposalsAttachmentCreateOwner, proposalInstance) ); - case AuthOp.ProposalsAttachmentRead: + case Action.ProposalsAttachmentRead: return ( - ability.can(AuthOp.ProposalsAttachmentReadAny, ProposalClass) || + ability.can(Action.ProposalsAttachmentReadAny, ProposalClass) || ability.can( - AuthOp.ProposalsAttachmentReadOwner, + Action.ProposalsAttachmentReadOwner, proposalInstance, ) || ability.can( - AuthOp.ProposalsAttachmentReadPublic, + Action.ProposalsAttachmentReadPublic, proposalInstance, ) || - ability.can(AuthOp.ProposalsAttachmentReadAccess, proposalInstance) + ability.can(Action.ProposalsAttachmentReadAccess, proposalInstance) ); - case AuthOp.ProposalsAttachmentUpdate: + case Action.ProposalsAttachmentUpdate: return ( - ability.can(AuthOp.ProposalsAttachmentUpdateAny, ProposalClass) || - ability.can(AuthOp.ProposalsAttachmentUpdateOwner, proposalInstance) + ability.can(Action.ProposalsAttachmentUpdateAny, ProposalClass) || + ability.can(Action.ProposalsAttachmentUpdateOwner, proposalInstance) ); - case AuthOp.ProposalsAttachmentDelete: + case Action.ProposalsAttachmentDelete: return ( - ability.can(AuthOp.ProposalsAttachmentDeleteAny, ProposalClass) || - ability.can(AuthOp.ProposalsAttachmentDeleteOwner, proposalInstance) + ability.can(Action.ProposalsAttachmentDeleteAny, ProposalClass) || + ability.can(Action.ProposalsAttachmentDeleteOwner, proposalInstance) ); default: @@ -167,7 +166,7 @@ export class ProposalsController { private async checkPermissionsForProposal( request: Request, id: string, - group: AuthOp, + group: Action, ) { const proposal = await this.proposalsService.findOne({ proposalId: id, @@ -190,7 +189,7 @@ export class ProposalsController { private async checkPermissionsForProposalCreate( request: Request, proposal: CreateProposalDto, - group: AuthOp, + group: Action, ) { if (!proposal) { throw new BadRequestException("Not able to create this proposal"); @@ -209,19 +208,19 @@ export class ProposalsController { mergedFilters.where = mergedFilters.where || {}; if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.ProposalsReadAny, ProposalClass); + const ability = this.caslAbilityFactory.proposalsInstanceAccess(user); + const canViewAll = ability.can(Action.ProposalsReadAny, ProposalClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.ProposalsReadManyAccess, + Action.ProposalsReadManyAccess, ProposalClass, ); const canViewOwner = ability.can( - AuthOp.ProposalsReadManyOwner, + Action.ProposalsReadManyOwner, ProposalClass, ); const canViewPublic = ability.can( - AuthOp.ProposalsReadManyPublic, + Action.ProposalsReadManyPublic, ProposalClass, ); if (canViewAccess) { @@ -244,8 +243,8 @@ export class ProposalsController { // POST /proposals @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsCreate, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsCreate, ProposalClass), ) @UseInterceptors( new MultiUTCTimeInterceptor( @@ -276,7 +275,7 @@ export class ProposalsController { const proposalDTO = await this.checkPermissionsForProposalCreate( request, createProposalDto, - AuthOp.ProposalsCreate, + Action.ProposalsCreate, ); const existingProposal = await this.proposalsService.findOne({ proposalId: createProposalDto.proposalId, @@ -292,8 +291,8 @@ export class ProposalsController { } @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsCreate, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsCreate, ProposalClass), ) @HttpCode(HttpStatus.OK) @Post("/isValid") @@ -331,8 +330,8 @@ export class ProposalsController { // GET /proposals @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsRead, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsRead, ProposalClass), ) @Get() @ApiOperation({ @@ -367,8 +366,8 @@ export class ProposalsController { // GET /proposals/fullquery @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsRead, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsRead, ProposalClass), ) @Get("/fullquery") @ApiOperation({ @@ -408,20 +407,20 @@ export class ProposalsController { const fields: IProposalFields = JSON.parse(filters.fields ?? "{}"); const limits: ILimitsFilter = JSON.parse(filters.limits ?? "{}"); if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.ProposalsReadAny, ProposalClass); + const ability = this.caslAbilityFactory.proposalsInstanceAccess(user); + const canViewAll = ability.can(Action.ProposalsReadAny, ProposalClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.ProposalsReadManyAccess, + Action.ProposalsReadManyAccess, ProposalClass, ); const canViewOwner = ability.can( - AuthOp.ProposalsReadManyOwner, + Action.ProposalsReadManyOwner, ProposalClass, ); const canViewPublic = ability.can( - AuthOp.ProposalsReadManyPublic, + Action.ProposalsReadManyPublic, ProposalClass, ); if (canViewAccess) { @@ -446,8 +445,8 @@ export class ProposalsController { // GET /proposals/fullfacet @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsRead, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsRead, ProposalClass), ) @Get("/fullfacet") @ApiOperation({ @@ -479,20 +478,20 @@ export class ProposalsController { const fields: IProposalFields = JSON.parse(filters.fields ?? "{}"); const facets = JSON.parse(filters.facets ?? "[]"); if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.ProposalsReadAny, ProposalClass); + const ability = this.caslAbilityFactory.proposalsInstanceAccess(user); + const canViewAll = ability.can(Action.ProposalsReadAny, ProposalClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.ProposalsReadManyAccess, + Action.ProposalsReadManyAccess, ProposalClass, ); const canViewOwner = ability.can( - AuthOp.ProposalsReadManyOwner, + Action.ProposalsReadManyOwner, ProposalClass, ); const canViewPublic = ability.can( - AuthOp.ProposalsReadManyPublic, + Action.ProposalsReadManyPublic, ProposalClass, ); if (canViewAccess) { @@ -516,10 +515,10 @@ export class ProposalsController { return this.proposalsService.fullfacet(parsedFilters); } - // GET /proposals/:id - @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsRead, ProposalClass), + // GET /proposals/:pid + @UseGuards(PoliciesGuard) + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsRead, ProposalClass), ) @Get("/:pid") @ApiOperation({ @@ -544,15 +543,54 @@ export class ProposalsController { const proposal = await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsRead, + Action.ProposalsRead, ); return proposal; } + // GET /proposals/:pid/authorization + @UseGuards(PoliciesGuard) + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsRead, ProposalClass), + ) + @Get("/:pid/authorization") + @ApiOperation({ + summary: "Check user access to a specific proposal.", + description: + "Returns a boolean indicating whether the user has access to the proposal with the specified ID.", + }) + @ApiParam({ + name: "pid", + description: "ID of the proposal to check access for", + type: String, + }) + @ApiResponse({ + status: HttpStatus.OK, + type: Boolean, + description: + "Returns true if the user has access to the specified proposal, otherwise false.", + }) + async findByIdAccess( + @Req() request: Request, + @Param("pid") proposalId: string, + ): Promise<{ canAccess: boolean }> { + const proposal = await this.proposalsService.findOne({ + proposalId, + }); + if (!proposal) return { canAccess: false }; + + const canAccess = await this.permissionChecker( + Action.ProposalsRead, + proposal, + request, + ); + return { canAccess }; + } + // PATCH /proposals/:pid @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsUpdate, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsUpdate, ProposalClass), ) @UseInterceptors( new MultiUTCTimeInterceptor( @@ -589,7 +627,7 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsUpdate, + Action.ProposalsUpdate, ); return this.proposalsService.update( { proposalId: proposalId }, @@ -599,8 +637,8 @@ export class ProposalsController { // DELETE /proposals/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsDelete, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsDelete, ProposalClass), ) @Delete("/:pid") @ApiOperation({ @@ -623,15 +661,15 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsDelete, + Action.ProposalsDelete, ); return this.proposalsService.remove({ proposalId: proposalId }); } // POST /proposals/:id/attachments @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsAttachmentCreate, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsAttachmentCreate, ProposalClass), ) @Post("/:pid/attachments") @ApiOperation({ @@ -663,7 +701,7 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsAttachmentCreate, + Action.ProposalsAttachmentCreate, ); const createAttachment = { @@ -675,8 +713,8 @@ export class ProposalsController { // GET /proposals/:pid/attachments @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsAttachmentRead, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsAttachmentRead, ProposalClass), ) @Get("/:pid/attachments") @ApiOperation({ @@ -704,15 +742,15 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsAttachmentRead, + Action.ProposalsAttachmentRead, ); return this.attachmentsService.findAll({ proposalId: proposalId }); } // PATCH /proposals/:pid/attachments/:aid @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsAttachmentUpdate, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsAttachmentUpdate, ProposalClass), ) @Patch("/:pid/attachments/:aid") @ApiOperation({ @@ -748,7 +786,7 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsAttachmentUpdate, + Action.ProposalsAttachmentUpdate, ); return this.attachmentsService.findOneAndUpdate( { _id: attachmentId, proposalId: proposalId }, @@ -758,8 +796,8 @@ export class ProposalsController { // DELETE /proposals/:pid/attachments/:aid @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsAttachmentDelete, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsAttachmentDelete, ProposalClass), ) @Delete("/:pid/attachments/:aid") @ApiOperation({ @@ -792,7 +830,7 @@ export class ProposalsController { await this.checkPermissionsForProposal( request, proposalId, - AuthOp.ProposalsAttachmentDelete, + Action.ProposalsAttachmentDelete, ); return this.attachmentsService.findOneAndDelete({ _id: attachmentId, @@ -802,8 +840,8 @@ export class ProposalsController { // GET /proposals/:id/datasets @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.ProposalsDatasetRead, ProposalClass), + @CheckPolicies("proposals", (ability: AppAbility) => + ability.can(Action.ProposalsDatasetRead, ProposalClass), ) @Get("/:pid/datasets") @ApiOperation({ @@ -830,21 +868,21 @@ export class ProposalsController { @Param("pid") proposalId: string, ): Promise { const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); + const ability = this.caslAbilityFactory.proposalsInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); const fields: IDatasetFields = JSON.parse("{}"); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewOwner = ability.can( - AuthOp.DatasetReadManyOwner, + Action.DatasetReadManyOwner, DatasetClass, ); const canViewPublic = ability.can( - AuthOp.DatasetReadManyPublic, + Action.DatasetReadManyPublic, DatasetClass, ); if (canViewAccess) { diff --git a/src/published-data/published-data.controller.ts b/src/published-data/published-data.controller.ts index 5ae805337..1eb9b2f12 100644 --- a/src/published-data/published-data.controller.ts +++ b/src/published-data/published-data.controller.ts @@ -31,7 +31,7 @@ import { import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { PublishedData, PublishedDataDocument, @@ -72,8 +72,8 @@ export class PublishedDataController { // POST /publisheddata @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Create, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Create, PublishedData), ) @Post() async create( @@ -158,8 +158,8 @@ export class PublishedDataController { // GET /publisheddata/formpopulate @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Read, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Read, PublishedData), ) @Get("/formpopulate") @ApiQuery({ @@ -224,8 +224,8 @@ export class PublishedDataController { // PATCH /publisheddata/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Update, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Update, PublishedData), ) @Patch("/:id") async update( @@ -240,8 +240,8 @@ export class PublishedDataController { // DELETE /publisheddata/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Delete, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Delete, PublishedData), ) @Delete("/:id") async remove(@Param("id") id: string): Promise { @@ -250,8 +250,8 @@ export class PublishedDataController { // POST /publisheddata/:id/register @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Update, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Update, PublishedData), ) @Post("/:id/register") async register(@Param("id") id: string): Promise { @@ -431,9 +431,31 @@ export class PublishedDataController { // POST /publisheddata/:id/resync @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.Update, PublishedData), + @CheckPolicies("publisheddata", (ability: AppAbility) => + ability.can(Action.Update, PublishedData), ) + @ApiOperation({ + summary: "Edits published data.", + description: + "It edits published data and resyncs with OAI Provider if it is defined.", + }) + @ApiParam({ + name: "id", + description: "The DOI of the published data.", + type: String, + }) + @ApiParam({ + name: "data", + description: + "The edited data that will be updated in the database and with OAI Provider if defined.", + type: UpdatePublishedDataDto, + }) + @ApiResponse({ + status: HttpStatus.OK, + isArray: false, + description: + "Return the result of resync with OAI Provider if defined, or null.", + }) @Post("/:id/resync") async resync( @Param("id") id: string, diff --git a/src/samples/samples.controller.ts b/src/samples/samples.controller.ts index 9c02a8ff8..8ea307d7c 100644 --- a/src/samples/samples.controller.ts +++ b/src/samples/samples.controller.ts @@ -34,7 +34,7 @@ import { import { PoliciesGuard } from "src/casl/guards/policies.guard"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { SampleClass, SampleDocument, @@ -63,6 +63,7 @@ import { Request } from "express"; import { JWTUser } from "src/auth/interfaces/jwt-user.interface"; import { IDatasetFields } from "src/datasets/interfaces/dataset-filters.interface"; import { CreateSubAttachmentDto } from "src/attachments/dto/create-sub-attachment.dto"; +import { AuthenticatedPoliciesGuard } from "src/casl/guards/auth-check.guard"; @ApiBearerAuth() @ApiTags("samples") @@ -87,7 +88,7 @@ export class SamplesController { return sampleInstance; } private async permissionChecker( - group: AuthOp, + group: Action, sample: SampleClass | CreateSampleDto, request: Request, ) { @@ -96,53 +97,53 @@ export class SamplesController { ); const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); + const ability = this.caslAbilityFactory.samplesInstanceAccess(user); try { switch (group) { - case AuthOp.SampleCreate: + case Action.SampleCreate: return ( - ability.can(AuthOp.SampleCreateAny, SampleClass) || - ability.can(AuthOp.SampleCreateOwner, sampleInstance) + ability.can(Action.SampleCreateAny, SampleClass) || + ability.can(Action.SampleCreateOwner, sampleInstance) ); - case AuthOp.SampleRead: + case Action.SampleRead: return ( - ability.can(AuthOp.SampleReadAny, SampleClass) || - ability.can(AuthOp.SampleReadOneOwner, sampleInstance) || - ability.can(AuthOp.SampleReadOneAccess, sampleInstance) || - ability.can(AuthOp.SampleReadOnePublic, sampleInstance) + ability.can(Action.SampleReadAny, SampleClass) || + ability.can(Action.SampleReadOneOwner, sampleInstance) || + ability.can(Action.SampleReadOneAccess, sampleInstance) || + ability.can(Action.SampleReadOnePublic, sampleInstance) ); - case AuthOp.SampleUpdate: + case Action.SampleUpdate: return ( - ability.can(AuthOp.SampleUpdateAny, SampleClass) || - ability.can(AuthOp.SampleUpdateOwner, sampleInstance) + ability.can(Action.SampleUpdateAny, SampleClass) || + ability.can(Action.SampleUpdateOwner, sampleInstance) ); - case AuthOp.SampleDelete: + case Action.SampleDelete: return ( - ability.can(AuthOp.SampleDeleteAny, SampleClass) || - ability.can(AuthOp.SampleDeleteOwner, sampleInstance) + ability.can(Action.SampleDeleteAny, SampleClass) || + ability.can(Action.SampleDeleteOwner, sampleInstance) ); - case AuthOp.SampleAttachmentCreate: + case Action.SampleAttachmentCreate: return ( - ability.can(AuthOp.SampleAttachmentCreateAny, SampleClass) || - ability.can(AuthOp.SampleAttachmentCreateOwner, sampleInstance) + ability.can(Action.SampleAttachmentCreateAny, SampleClass) || + ability.can(Action.SampleAttachmentCreateOwner, sampleInstance) ); - case AuthOp.SampleAttachmentRead: + case Action.SampleAttachmentRead: return ( - ability.can(AuthOp.SampleAttachmentReadAny, SampleClass) || - ability.can(AuthOp.SampleAttachmentReadOwner, sampleInstance) || - ability.can(AuthOp.SampleAttachmentReadPublic, sampleInstance) || - ability.can(AuthOp.SampleAttachmentReadAccess, sampleInstance) + ability.can(Action.SampleAttachmentReadAny, SampleClass) || + ability.can(Action.SampleAttachmentReadOwner, sampleInstance) || + ability.can(Action.SampleAttachmentReadPublic, sampleInstance) || + ability.can(Action.SampleAttachmentReadAccess, sampleInstance) ); - case AuthOp.SampleAttachmentUpdate: + case Action.SampleAttachmentUpdate: return ( - ability.can(AuthOp.SampleAttachmentUpdateAny, SampleClass) || - ability.can(AuthOp.SampleAttachmentUpdateOwner, sampleInstance) + ability.can(Action.SampleAttachmentUpdateAny, SampleClass) || + ability.can(Action.SampleAttachmentUpdateOwner, sampleInstance) ); - case AuthOp.SampleAttachmentDelete: + case Action.SampleAttachmentDelete: return ( - ability.can(AuthOp.SampleAttachmentDeleteAny, SampleClass) || - ability.can(AuthOp.SampleAttachmentDeleteOwner, sampleInstance) + ability.can(Action.SampleAttachmentDeleteAny, SampleClass) || + ability.can(Action.SampleAttachmentDeleteOwner, sampleInstance) ); default: @@ -157,7 +158,7 @@ export class SamplesController { private async checkPermissionsForSample( request: Request, id: string, - group: AuthOp, + group: Action, ) { const sample = await this.samplesService.findOne({ sampleId: id, @@ -177,7 +178,7 @@ export class SamplesController { private async checkPermissionsForSampleCreate( request: Request, sample: CreateSampleDto, - group: AuthOp, + group: Action, ) { if (!sample) { throw new BadRequestException("Not able to create this sample"); @@ -198,19 +199,19 @@ export class SamplesController { /* eslint-disable @typescript-eslint/no-explicit-any */ const authorizationFilter: Record = { where: {} }; if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.SampleReadAny, SampleClass); + const ability = this.caslAbilityFactory.samplesInstanceAccess(user); + const canViewAll = ability.can(Action.SampleReadAny, SampleClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.SampleReadManyAccess, + Action.SampleReadManyAccess, SampleClass, ); const canViewOwner = ability.can( - AuthOp.SampleReadManyOwner, + Action.SampleReadManyOwner, SampleClass, ); const canViewPublic = ability.can( - AuthOp.SampleReadManyPublic, + Action.SampleReadManyPublic, SampleClass, ); @@ -242,9 +243,9 @@ export class SamplesController { return mergedFilters; } // POST /samples - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleCreate, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleCreate, SampleClass), ) @UseInterceptors( new FormatPhysicalQuantitiesInterceptor( @@ -274,15 +275,15 @@ export class SamplesController { const sampleDTO = await this.checkPermissionsForSampleCreate( request, createSampleDto, - AuthOp.SampleCreate, + Action.SampleCreate, ); return this.samplesService.create(sampleDTO); } // GET /samples @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleRead, SampleClass), + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), ) @Get() @ApiOperation({ @@ -314,9 +315,9 @@ export class SamplesController { } // GET /samples/fullquery - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleRead, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), ) @Get("/fullquery") @ApiOperation({ @@ -356,20 +357,20 @@ export class SamplesController { const fields: ISampleFields = JSON.parse(filters.fields ?? "{}"); const limits: ILimitsFilter = JSON.parse(filters.limits ?? "{}"); if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.SampleReadAny, SampleClass); + const ability = this.caslAbilityFactory.samplesInstanceAccess(user); + const canViewAll = ability.can(Action.SampleReadAny, SampleClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.SampleReadManyAccess, + Action.SampleReadManyAccess, SampleClass, ); const canViewOwner = ability.can( - AuthOp.SampleReadManyOwner, + Action.SampleReadManyOwner, SampleClass, ); const canViewPublic = ability.can( - AuthOp.SampleReadManyPublic, + Action.SampleReadManyPublic, SampleClass, ); if (canViewAccess) { @@ -391,9 +392,9 @@ export class SamplesController { } // GET /samples/metadataKeys - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleRead, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), ) @Get("/metadataKeys") @ApiOperation({ @@ -427,20 +428,20 @@ export class SamplesController { const fields: ISampleFields = JSON.parse(filters.fields ?? "{}"); const limits: ILimitsFilter = JSON.parse(filters.limits ?? "{}"); if (user) { - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAll = ability.can(AuthOp.SampleReadAny, SampleClass); + const ability = this.caslAbilityFactory.samplesInstanceAccess(user); + const canViewAll = ability.can(Action.SampleReadAny, SampleClass); if (!canViewAll) { const canViewAccess = ability.can( - AuthOp.SampleReadManyAccess, + Action.SampleReadManyAccess, SampleClass, ); const canViewOwner = ability.can( - AuthOp.SampleReadManyOwner, + Action.SampleReadManyOwner, SampleClass, ); const canViewPublic = ability.can( - AuthOp.SampleReadManyPublic, + Action.SampleReadManyPublic, SampleClass, ); if (canViewAccess) { @@ -463,9 +464,9 @@ export class SamplesController { } // GET /samples/findOne - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleRead, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), ) @Get("/findOne") @ApiOperation({ @@ -523,8 +524,8 @@ export class SamplesController { // GET /samples/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleRead, SampleClass), + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), ) @Get("/:id") @Header("content-type", "application/json") @@ -546,14 +547,52 @@ export class SamplesController { @Req() request: Request, @Param("id") id: string, ): Promise { - await this.checkPermissionsForSample(request, id, AuthOp.SampleRead); + await this.checkPermissionsForSample(request, id, Action.SampleRead); return this.samplesService.findOne({ sampleId: id }); } // PATCH /samples/:id @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleUpdate, SampleClass), + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleRead, SampleClass), + ) + @Get("/:id/authorization") + @ApiOperation({ + summary: "Check user access to a specific sample.", + description: + "Returns a boolean indicating whether the user has access to the sample with the specified ID.", + }) + @ApiParam({ + name: "pid", + description: "ID of the sample to check access for", + type: String, + }) + @ApiResponse({ + status: HttpStatus.OK, + type: Boolean, + description: + "Returns true if the user has access to the specified sample, otherwise false.", + }) + async findByIdAccess( + @Req() request: Request, + @Param("id") id: string, + ): Promise<{ canAccess: boolean }> { + const sample = await this.samplesService.findOne({ + sampleId: id, + }); + if (!sample) return { canAccess: false }; + const canAccess = await this.permissionChecker( + Action.SampleRead, + sample, + request, + ); + return { canAccess }; + } + + // PATCH /samples/:id + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleUpdate, SampleClass), ) @UseInterceptors( new FormatPhysicalQuantitiesInterceptor( @@ -586,14 +625,14 @@ export class SamplesController { @Param("id") id: string, @Body() updateSampleDto: PartialUpdateSampleDto, ): Promise { - await this.checkPermissionsForSample(request, id, AuthOp.SampleUpdate); + await this.checkPermissionsForSample(request, id, Action.SampleUpdate); return this.samplesService.update({ sampleId: id }, updateSampleDto); } // DELETE /samples/:id @UseGuards() - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleDelete, SampleClass), + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleDelete, SampleClass), ) @Delete("/:id") @ApiOperation({ @@ -613,14 +652,14 @@ export class SamplesController { @Req() request: Request, @Param("id") id: string, ): Promise { - await this.checkPermissionsForSample(request, id, AuthOp.SampleDelete); + await this.checkPermissionsForSample(request, id, Action.SampleDelete); return this.samplesService.remove({ sampleId: id }); } // POST /samples/:id/attachments - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleAttachmentDelete, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleAttachmentDelete, SampleClass), ) @Post("/:id/attachments") @ApiOperation({ @@ -651,7 +690,7 @@ export class SamplesController { const sample = await this.checkPermissionsForSample( request, id, - AuthOp.SampleAttachmentCreate, + Action.SampleAttachmentCreate, ); if (!sample) { throw new BadRequestException( @@ -671,8 +710,8 @@ export class SamplesController { // GET /samples/:id/attachments @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleAttachmentRead, SampleClass), + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleAttachmentRead, SampleClass), ) @Get("/:id/attachments") @ApiOperation({ @@ -698,15 +737,15 @@ export class SamplesController { await this.checkPermissionsForSample( request, id, - AuthOp.SampleAttachmentRead, + Action.SampleAttachmentRead, ); return this.attachmentsService.findAll({ sampleId: id }); } // GET /samples/:id/attachments/:fk - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleAttachmentRead, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleAttachmentRead, SampleClass), ) @Get("/:id/attachments/:fk") @ApiOperation({ @@ -738,7 +777,7 @@ export class SamplesController { await this.checkPermissionsForSample( request, sampleId, - AuthOp.SampleAttachmentRead, + Action.SampleAttachmentRead, ); return this.attachmentsService.findOne({ id: attachmentId, @@ -747,9 +786,9 @@ export class SamplesController { } // DELETE /samples/:id/attachments/:fk - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleAttachmentDelete, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleAttachmentDelete, SampleClass), ) @Delete("/:id/attachments/:fk") @ApiOperation({ @@ -780,7 +819,7 @@ export class SamplesController { await this.checkPermissionsForSample( request, sampleId, - AuthOp.SampleAttachmentDelete, + Action.SampleAttachmentDelete, ); return this.attachmentsService.findOneAndDelete({ _id: attachmentId, @@ -789,8 +828,8 @@ export class SamplesController { } // POST /samples/:id/datasets - /* @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Create, Dataset)) + /* @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => ability.can(Action.Create, Dataset)) @Post("/:id/datasets") async createDataset( @Param("id") id: string, @@ -802,9 +841,9 @@ export class SamplesController { */ // GET /samples/:id/datasets - @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.SampleDatasetRead, SampleClass), + @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => + ability.can(Action.SampleDatasetRead, SampleClass), ) @Get("/:id/datasets") @ApiOperation({ @@ -828,21 +867,21 @@ export class SamplesController { @Param("id") sampleId: string, ): Promise { const user: JWTUser = request.user as JWTUser; - const ability = this.caslAbilityFactory.createForUser(user); - const canViewAny = ability.can(AuthOp.DatasetReadAny, DatasetClass); + const ability = this.caslAbilityFactory.samplesInstanceAccess(user); + const canViewAny = ability.can(Action.DatasetReadAny, DatasetClass); const fields: IDatasetFields = JSON.parse("{}"); if (!canViewAny) { const canViewAccess = ability.can( - AuthOp.DatasetReadManyAccess, + Action.DatasetReadManyAccess, DatasetClass, ); const canViewOwner = ability.can( - AuthOp.DatasetReadManyOwner, + Action.DatasetReadManyOwner, DatasetClass, ); const canViewPublic = ability.can( - AuthOp.DatasetReadManyPublic, + Action.DatasetReadManyPublic, DatasetClass, ); if (canViewAccess) { @@ -865,8 +904,8 @@ export class SamplesController { } // PATCH /samples/:id/datasets/:fk - /* @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Update, Dataset)) + /* @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => ability.can(Action.Update, Dataset)) @Patch("/:id/datasets/:fk") async findOneDatasetAndUpdate( @Param("id") sampleId: string, @@ -880,8 +919,8 @@ export class SamplesController { } */ // DELETE /samples/:id/datasets/:fk - /* @UseGuards(PoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.Delete, Dataset)) + /* @UseGuards(AuthenticatedPoliciesGuard) + @CheckPolicies("samples", (ability: AppAbility) => ability.can(Action.Delete, Dataset)) @Delete("/:id/datasets/:fk") async findOneDatasetAndRemove( @Param("id") sampleId: string, diff --git a/src/users/user-identities.controller.ts b/src/users/user-identities.controller.ts index 5c9b027aa..99fa923f2 100644 --- a/src/users/user-identities.controller.ts +++ b/src/users/user-identities.controller.ts @@ -9,7 +9,7 @@ import { UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiTags } from "@nestjs/swagger"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { UserIdentity } from "./schemas/user-identity.schema"; @@ -30,9 +30,10 @@ export class UserIdentitiesController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserReadOwn, User) || - ability.can(AuthOp.UserReadAny, User), + ability.can(Action.UserReadOwn, User) || + ability.can(Action.UserReadAny, User), ) @Get("/findOne") async findOne( @@ -56,11 +57,11 @@ export class UserIdentitiesController { const authenticatedUser: JWTUser = request.user as JWTUser; const ability = - await this.caslAbilityFactory.createForUser(authenticatedUser); + await this.caslAbilityFactory.userEndpointAccess(authenticatedUser); if ( - !ability.can(AuthOp.UserReadAny, User) && - ability.can(AuthOp.UserReadOwn, User) + !ability.can(Action.UserReadAny, User) && + ability.can(Action.UserReadOwn, User) ) { // this user can only see his/her user identity filter = { userId: authenticatedUser._id, ...filter }; @@ -78,8 +79,8 @@ export class UserIdentitiesController { user._id = identity.userId; user.id = identity.userId; if ( - !ability.can(AuthOp.UserReadOwn, user) && - !ability.can(AuthOp.UserReadAny, User) + !ability.can(Action.UserReadOwn, user) && + !ability.can(Action.UserReadAny, User) ) { throw new ForbiddenException("Access Forbidden or Unauthorized"); } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index d03908b85..45a13269d 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -20,7 +20,7 @@ import { ApiResponse, ApiTags, } from "@nestjs/swagger"; -import { AuthOp } from "src/casl/authop.enum"; +import { Action } from "src/casl/action.enum"; import { AppAbility, CaslAbilityFactory } from "src/casl/casl-ability.factory"; import { CheckPolicies } from "src/casl/decorators/check-policies.decorator"; import { UserIdentity } from "./schemas/user-identity.schema"; @@ -60,7 +60,7 @@ export class UsersController { async checkUserAuthorization( request: Request, - actions: AuthOp[], + actions: Action[], viewedUserId: string, ) { const authenticatedUser: JWTUser = request.user as JWTUser; @@ -68,7 +68,8 @@ export class UsersController { viewedUserSchema._id = viewedUserId; viewedUserSchema.id = viewedUserId; - const ability = this.caslAbilityFactory.createForUser(authenticatedUser); + const ability = + this.caslAbilityFactory.userEndpointAccess(authenticatedUser); // const authorized = actions.map( action => // ability.can(action, viewedUserSchema) // ) as Array; @@ -115,7 +116,9 @@ export class UsersController { } @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.UserReadOwn, User)) + @CheckPolicies("users", (ability: AppAbility) => + ability.can(Action.UserReadOwn, User), + ) @UseInterceptors(CreateUserSettingsInterceptor) @Get("/my/self") @ApiOperation({ @@ -133,14 +136,16 @@ export class UsersController { const authenticatedUserId: string = (request.user as JWTUser)._id; await this.checkUserAuthorization( request, - [AuthOp.UserReadOwn], + [Action.UserReadOwn], (request.user as JWTUser)._id, ); return this.usersService.findById(authenticatedUserId); } @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.UserReadOwn, User)) + @CheckPolicies("users", (ability: AppAbility) => + ability.can(Action.UserReadOwn, User), + ) @Get("/my/identity") async getMyUserIdentity( @Req() request: Request, @@ -148,20 +153,22 @@ export class UsersController { const authenticatedUserId: string = (request.user as JWTUser)._id; await this.checkUserAuthorization( request, - [AuthOp.UserReadOwn], + [Action.UserReadOwn], authenticatedUserId, ); return this.usersService.findByIdUserIdentity(authenticatedUserId); } @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => ability.can(AuthOp.UserReadOwn, User)) + @CheckPolicies("users", (ability: AppAbility) => + ability.can(Action.UserReadOwn, User), + ) @Get("/my/settings") async getMySettings(@Req() request: Request): Promise { const authenticatedUserId: string = (request.user as JWTUser)._id; await this.checkUserAuthorization( request, - [AuthOp.UserReadOwn], + [Action.UserReadOwn], authenticatedUserId, ); return this.usersService.findByIdUserSettings(authenticatedUserId); @@ -169,9 +176,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserReadOwn, User) || - ability.can(AuthOp.UserReadAny, User), + ability.can(Action.UserReadOwn, User) || + ability.can(Action.UserReadAny, User), ) @UseInterceptors(CreateUserSettingsInterceptor) @Get("/:id") @@ -181,7 +189,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserReadAny, AuthOp.UserReadOwn], + [Action.UserReadAny, Action.UserReadOwn], id, ); return this.usersService.findById(id); @@ -189,9 +197,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserReadOwn, User) || - ability.can(AuthOp.UserReadAny, User), + ability.can(Action.UserReadOwn, User) || + ability.can(Action.UserReadAny, User), ) @Get(":id/userIdentity") async getUserIdentity( @@ -200,7 +209,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserReadAny, AuthOp.UserReadOwn], + [Action.UserReadAny, Action.UserReadOwn], id, ); return this.usersService.findByIdUserIdentity(id); @@ -208,9 +217,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserCreateOwn, User) || - ability.can(AuthOp.UserCreateAny, User), + ability.can(Action.UserCreateOwn, User) || + ability.can(Action.UserCreateAny, User), ) @Post("/:id/settings") async createSettings( @@ -220,7 +230,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserCreateAny, AuthOp.UserCreateOwn], + [Action.UserCreateAny, Action.UserCreateOwn], id, ); return this.usersService.createUserSettings(id, createUserSettingsDto); @@ -228,9 +238,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserReadOwn, User) || - ability.can(AuthOp.UserReadAny, User), + ability.can(Action.UserReadOwn, User) || + ability.can(Action.UserReadAny, User), ) @Get("/:id/settings") async getSettings( @@ -239,7 +250,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserReadAny, AuthOp.UserReadOwn], + [Action.UserReadAny, Action.UserReadOwn], id, ); return this.usersService.findByIdUserSettings(id); @@ -247,9 +258,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserUpdateOwn, User) || - ability.can(AuthOp.UserUpdateAny, User), + ability.can(Action.UserUpdateOwn, User) || + ability.can(Action.UserUpdateAny, User), ) @Put("/:id/settings") async updateSettings( @@ -259,7 +271,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserUpdateAny, AuthOp.UserUpdateOwn], + [Action.UserUpdateAny, Action.UserUpdateOwn], id, ); return this.usersService.findOneAndUpdateUserSettings( @@ -270,9 +282,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserUpdateOwn, User) || - ability.can(AuthOp.UserUpdateAny, User), + ability.can(Action.UserUpdateOwn, User) || + ability.can(Action.UserUpdateAny, User), ) @Patch("/:id/settings") async patchSettings( @@ -282,7 +295,7 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserUpdateAny, AuthOp.UserUpdateOwn], + [Action.UserUpdateAny, Action.UserUpdateOwn], id, ); return this.usersService.findOneAndUpdateUserSettings( @@ -293,9 +306,10 @@ export class UsersController { @UseGuards(AuthenticatedPoliciesGuard) @CheckPolicies( + "users", (ability: AppAbility) => - ability.can(AuthOp.UserDeleteOwn, User) || - ability.can(AuthOp.UserDeleteAny, User), + ability.can(Action.UserDeleteOwn, User) || + ability.can(Action.UserDeleteAny, User), ) @Delete("/:id/settings") async removeSettings( @@ -304,17 +318,17 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserUpdateAny, AuthOp.UserUpdateOwn], + [Action.UserUpdateAny, Action.UserUpdateOwn], id, ); return this.usersService.findOneAndDeleteUserSettings(id); } @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => { + @CheckPolicies("users", (ability: AppAbility) => { return ( - ability.can(AuthOp.UserReadOwn, User) || - ability.can(AuthOp.UserReadAny, User) + ability.can(Action.UserReadOwn, User) || + ability.can(Action.UserReadAny, User) ); }) @Get("/:id/authorization/dataset/create") @@ -324,16 +338,16 @@ export class UsersController { ): Promise { await this.checkUserAuthorization( request, - [AuthOp.UserReadAny, AuthOp.UserReadOwn], + [Action.UserReadAny, Action.UserReadOwn], id, ); const viewedUser = (await this.usersService.findById2JWTUser( id, )) as JWTUser; - const ability = this.caslAbilityFactory.createForUser(viewedUser); + const ability = this.caslAbilityFactory.datasetEndpointAccess(viewedUser); - const canCreateDataset = ability.can(AuthOp.DatasetCreate, DatasetClass); + const canCreateDataset = ability.can(Action.DatasetCreate, DatasetClass); return { authorization: canCreateDataset, @@ -356,8 +370,8 @@ export class UsersController { } @UseGuards(AuthenticatedPoliciesGuard) - @CheckPolicies((ability: AppAbility) => - ability.can(AuthOp.UserCreateJwt, User), + @CheckPolicies("users", (ability: AppAbility) => + ability.can(Action.UserCreateJwt, User), ) @Post("/:id/jwt") @ApiOperation({ diff --git a/test/DatasetAuthorization.js b/test/DatasetAuthorization.js index bcecc2b03..85db36792 100644 --- a/test/DatasetAuthorization.js +++ b/test/DatasetAuthorization.js @@ -695,7 +695,6 @@ describe("0300: DatasetAuthorization: Test access to dataset", () => { pid: TestData.PidPrefix + "/" + uuidv4(), ownerGroup: "admin", }; - console.log("0502: pid : " + datasetWithPid["pid"]); return request(appUrl) .post("/api/v3/Datasets") diff --git a/test/ProposalAuthorization.js b/test/ProposalAuthorization.js index 4e4a9a200..bdb220770 100644 --- a/test/ProposalAuthorization.js +++ b/test/ProposalAuthorization.js @@ -157,7 +157,7 @@ describe("1400: ProposalAuthorization: Test access to proposal", () => { .get("/api/v3/proposals/" + encodedProposalPid2) .set("Accept", "application/json") .expect("Content-Type", /json/) - .expect(TestData.UnauthorizedStatusCode); + .expect(TestData.AccessForbiddenStatusCode); }); it("0050: admin can list all proposals", async () => { diff --git a/test/SampleAuthorization.js b/test/SampleAuthorization.js index a8fcfcb25..665d1d8ee 100644 --- a/test/SampleAuthorization.js +++ b/test/SampleAuthorization.js @@ -485,7 +485,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples") .send(sample) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0180: adds a new sample as an unauthenticated user with owner group as sampleingestor, which should fail", async () => { @@ -498,7 +498,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples") .send(sample) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0190: adds a new sample as an unauthenticated user with owner group as user1, which should fail", async () => { @@ -511,7 +511,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples") .send(sample) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0200: adds a new sample as an unauthenticated user with its owner group, which should fail", async () => { @@ -524,7 +524,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples") .send(sample) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0210: adds a new sample as Archive Manager with owner group as adminingestor, which should fail", async () => { @@ -977,7 +977,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples/" + sampleId1 + "/attachments") .send(TestData.AttachmentCorrect) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0420: adds an attachment as an unauthenticated user to sample 2, which should fail", async () => { @@ -985,7 +985,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples/" + sampleId2 + "/attachments") .send(TestData.AttachmentCorrect) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0430: adds an attachment as an unauthenticated user to sample 3, which should fail", async () => { @@ -993,7 +993,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples/" + sampleId3 + "/attachments") .send(TestData.AttachmentCorrect) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0440: adds an attachment as an unauthenticated user to sample 4, which should fail", async () => { @@ -1001,7 +1001,7 @@ describe("2250: Sample Authorization", () => { .post("/api/v3/Samples/" + sampleId4 + "/attachments") .send(TestData.AttachmentCorrect) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("0450: adds an attachment as Archive Manager to sample 1, which should fail", async () => { @@ -3576,7 +3576,7 @@ describe("2250: Sample Authorization", () => { .patch("/api/v3/Samples/" + sampleId1) .send({ sampleCharacteristics: characteristics }) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("2280: update sample characteristic for public sample 10 as Unauthenticated User, which should fail", async () => { @@ -3592,7 +3592,7 @@ describe("2250: Sample Authorization", () => { .patch("/api/v3/Samples/" + sampleId10) .send({ sampleCharacteristics: characteristics }) .set("Accept", "application/json") - .expect(TestData.CreationForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); // delete sample attachment @@ -3600,7 +3600,7 @@ describe("2250: Sample Authorization", () => { return request(appUrl) .delete("/api/v3/samples/" + sampleId1 + "/attachments/" + attachmentId1) .set("Accept", "application/json") - .expect(TestData.DeleteForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("4010: delete attachment 10 from sample 10 as Unauthenticated User, which should fail", async () => { @@ -3609,7 +3609,7 @@ describe("2250: Sample Authorization", () => { "/api/v3/samples/" + sampleId10 + "/attachments/" + attachmentId10, ) .set("Accept", "application/json") - .expect(TestData.DeleteForbiddenStatusCode); + .expect(TestData.CreationUnauthorizedStatusCode); }); it("4020: delete attachment 1 from sample 1 as User 5, which should fail", async () => { diff --git a/test/TestData.js b/test/TestData.js index a22884445..37f1d147f 100644 --- a/test/TestData.js +++ b/test/TestData.js @@ -18,6 +18,7 @@ const TestData = { BadRequestStatusCode: 400, AccessForbiddenStatusCode: 403, UnauthorizedStatusCode: 401, + CreationUnauthorizedStatusCode: 401, ConflictStatusCode: 409, ApplicationErrorStatusCode: 500, LoginSuccessfulStatusCode: 201, diff --git a/test/config/jobconfig.json b/test/config/jobconfig.json index 6f2247141..146043018 100644 --- a/test/config/jobconfig.json +++ b/test/config/jobconfig.json @@ -70,4 +70,4 @@ } } ] -} +} \ No newline at end of file