Skip to content
68 changes: 68 additions & 0 deletions src/casl/abilities/published-data.ability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
AbilityBuilder,
ExtractSubjectType,
MongoAbility,
createMongoAbility,
} from "@casl/ability";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { AccessGroupsType } from "src/config/configuration";
import { Action } from "../action.enum";
import {
Subjects,
PossibleAbilities,
Conditions,
} from "../types/casl-subjects";
import { JWTUser } from "src/auth/interfaces/jwt-user.interface";
import { PublishedData } from "src/published-data/schemas/published-data.schema";

@Injectable()
export class PublishedDataAbility {
constructor(private configService: ConfigService) {
this.accessGroups =
this.configService.get<AccessGroupsType>("accessGroups");
}
private accessGroups;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated

buildAbility(user: JWTUser): MongoAbility<PossibleAbilities, Conditions> {
const { can, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
);

/**
* Unauthenticated user
*/
if (!user) {
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}

/**
* Authenticated user
*/
can(Action.Create, PublishedData);
can(Action.Read, PublishedData);
can(Action.Update, PublishedData);

if (user.currentGroups.some((g) => this.accessGroups?.admin.includes(g))) {
/**
* User belonging to ADMIN_GROUPS
*/
can(Action.AccessAny, PublishedData);
}

if (user.currentGroups.some((g) => this.accessGroups?.delete.includes(g))) {
/**
* User belonging to DELETE_GROUPS
*/
can(Action.Delete, PublishedData);
}

return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
}
56 changes: 7 additions & 49 deletions src/casl/casl-ability.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import { Opensearch } from "src/opensearch/opensearch.subject";
import { OrigDatablock } from "src/origdatablocks/schemas/origdatablock.schema";
import { Policy } from "src/policies/schemas/policy.schema";
import { ProposalClass } from "src/proposals/schemas/proposal.schema";
import { PublishedData } from "src/published-data/schemas/published-data.schema";
import { RuntimeConfig } from "src/config/runtime-config/schemas/runtime-config.schema";
import { SampleClass } from "src/samples/schemas/sample.schema";
import { User } from "src/users/schemas/user.schema";
import { Action } from "./action.enum";
import { Subjects, PossibleAbilities, Conditions } from "./types/casl-subjects";
import { PublishedDataAbility } from "./abilities/published-data.ability";

export type AppAbility = MongoAbility<PossibleAbilities, Conditions>;

Expand All @@ -37,6 +37,7 @@ export class CaslAbilityFactory {
constructor(
private configService: ConfigService,
private jobConfigService: JobConfigService,
private publishedDataAbility: PublishedDataAbility,
) {
this.accessGroups =
this.configService.get<AccessGroupsType>("accessGroups");
Expand All @@ -58,7 +59,7 @@ export class CaslAbilityFactory {
origdatablocks: this.origDatablockEndpointAccess,
policies: this.policyEndpointAccess,
proposals: this.proposalsEndpointAccess,
publisheddata: this.publishedDataEndpointAccess,
publisheddata: this.publishedDataAccess,
runtimeconfig: this.runtimeConfigEndpointAccess,
samples: this.samplesEndpointAccess,
users: this.userEndpointAccess,
Expand All @@ -74,6 +75,10 @@ export class CaslAbilityFactory {
return accessFunction.call(this, user);
}

publishedDataAccess(user: JWTUser) {
return this.publishedDataAbility.buildAbility(user);
}

datasetEndpointAccess(user: JWTUser) {
const { can, cannot, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
Expand Down Expand Up @@ -1063,31 +1068,6 @@ export class CaslAbilityFactory {
});
}

publishedDataEndpointAccess(user: JWTUser) {
const { can, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
);
if (user) {
can(Action.Read, PublishedData);
can(Action.Update, PublishedData);
can(Action.Create, PublishedData);
}

if (
user &&
user.currentGroups.some((g) => this.accessGroups?.delete.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<Subjects>,
});
}

samplesEndpointAccess(user: JWTUser) {
const { can, cannot, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
Expand Down Expand Up @@ -2357,28 +2337,6 @@ export class CaslAbilityFactory {
});
}

publishedDataInstanceAccess(user: JWTUser) {
const { can, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
);

if (
user &&
user.currentGroups.some((g) => this.accessGroups?.admin.includes(g))
) {
// -------------------------------------
// users belonging to any of the group listed in ADMIN_GROUPS
// -------------------------------------

can(Action.AccessAny, PublishedData);
}

return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}

datablockInstanceAccess(user: JWTUser) {
const { can, build } = new AbilityBuilder(
createMongoAbility<PossibleAbilities, Conditions>,
Expand Down
4 changes: 3 additions & 1 deletion src/casl/casl.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { CaslAbilityFactory } from "./casl-ability.factory";
import { JobConfigModule } from "src/config/job-config/jobconfig.module";
import { PublishedDataAbility } from "./abilities/published-data.ability";

@Module({
imports: [JobConfigModule, ConfigModule],
providers: [CaslAbilityFactory],
providers: [CaslAbilityFactory, PublishedDataAbility],
exports: [CaslAbilityFactory],
})
export class CaslModule {}
14 changes: 7 additions & 7 deletions src/published-data/published-data.v4.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class PublishedDataV4Controller {
publishedDataFilters.limits = publishedDataLimits;
}

const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down Expand Up @@ -189,7 +189,7 @@ export class PublishedDataV4Controller {
) {
const jsonFilters: IPublishedDataFilters = filter?.filter ?? {};

const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down Expand Up @@ -287,7 +287,7 @@ export class PublishedDataV4Controller {
const filter: FilterQuery<PublishedData> = {
doi,
};
const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);
if (ability.cannot(Action.AccessAny, PublishedData)) {
Expand Down Expand Up @@ -365,7 +365,7 @@ export class PublishedDataV4Controller {
throw new NotFoundException(`Published data with id ${id} not found.`);
}

const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down Expand Up @@ -464,7 +464,7 @@ export class PublishedDataV4Controller {
@Req() request: Request,
@Param("id") id: string,
): Promise<PublishedData | null> {
const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down Expand Up @@ -513,7 +513,7 @@ export class PublishedDataV4Controller {
throw new NotFoundException(`Published data with id ${id} not found.`);
}

const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down Expand Up @@ -679,7 +679,7 @@ export class PublishedDataV4Controller {
throw new NotFoundException(`Published data with id ${id} not found.`);
}

const ability = this.caslAbilityFactory.publishedDataInstanceAccess(
const ability = this.caslAbilityFactory.publishedDataAccess(
request.user as JWTUser,
);

Expand Down
Loading