Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jobs tests #1286

Merged
merged 46 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
fbc463b
first tests
sofyalaski May 8, 2024
0592631
feat: reset dataset before every api test (#1131)
Junjiequan Apr 2, 2024
bf8fc0f
Fixes after jobs testing:
sofyalaski May 23, 2024
0d586fc
Fixes after jobs testing:
sofyalaski May 23, 2024
54ee6a6
Fixes after testing:
sofyalaski May 23, 2024
f871c62
dataset(s)Validation redundancy after a merged PR
sofyalaski May 23, 2024
8b8f470
test Jobs authorization wip
sofyalaski May 23, 2024
ea76968
fix filtering syntax
sofyalaski Jun 11, 2024
c7fc938
fixes after Job testing:
sofyalaski Jun 11, 2024
39f12b4
minor changes
sofyalaski Jun 11, 2024
2716245
fix findOne filtering syntax
sofyalaski Jun 11, 2024
186ac28
add Jobs tests
sofyalaski Jun 11, 2024
5edb913
remove redundancy
sofyalaski Jun 13, 2024
c6fd75b
fix numbering
sofyalaski Jun 13, 2024
75f41d6
extend UPDATE_JOB_GROUP user's right to match docs
sofyalaski Jun 14, 2024
83a2743
remove unneccessary arguments
sofyalaski Jun 14, 2024
7b80f14
make function return value of findOne an instance of JobClass
sofyalaski Jun 14, 2024
52a791b
add statusUpdate tests
sofyalaski Jun 14, 2024
af31a2a
add jobParams to the job instance
sofyalaski Jun 17, 2024
c7dd474
Patch tests
sofyalaski Jun 17, 2024
6630770
merge changes from release-jobs
sofyalaski Jun 20, 2024
ea61e32
Fixes after tests:
sofyalaski Jun 25, 2024
4e7c618
add deleteJobGroups from env to configuration
sofyalaski Jun 25, 2024
56a66ad
add username for anonymous user when creating/updating status of the job
sofyalaski Jun 25, 2024
fc8c3ae
statusUpdate instead of update
sofyalaski Jun 25, 2024
47cef8e
Fixes after testing:
sofyalaski Jun 25, 2024
2718699
Finalized tests for authorization
sofyalaski Jun 25, 2024
29287bf
add delete_job_groups definition
sofyalaski Jun 25, 2024
8810393
fix unit tests after test jobconfig.json changes
sofyalaski Jun 26, 2024
31d148e
empty collections before testing
sofyalaski Jul 1, 2024
e274398
changes requested in PR
sofyalaski Jul 1, 2024
6f50600
eremove version duplicate
sofyalaski Jul 2, 2024
eb1962f
Merge remote-tracking branch 'upstream/release-jobs' into jobs-tests
sofyalaski Jul 11, 2024
070e964
change getJobMatchingConfiguration function to explicitly use job typ…
sofyalaski Jul 14, 2024
9fa1b8e
run full testing workflow on pushing to release branches
sofyalaski Jul 14, 2024
759ddfb
add env variable for jobs config path for github workflow
sofyalaski Jul 14, 2024
6667320
Fix lint errors
sbliven Jul 23, 2024
7655856
Merge branch 'release-jobs' into jobs-tests
sbliven Jul 23, 2024
420f1df
Fix lint following merge
sbliven Jul 23, 2024
46ade45
Update test/config/pretest.js to use "dotenv"
despadam Jul 25, 2024
8a07ea2
Fix pretest.js after merging suggestions
despadam Jul 25, 2024
5210a82
Fix pretest.js
despadam Jul 25, 2024
9df58ba
Fix jobconfig no-explicit-any issues
sbliven Jul 26, 2024
7c9c8ae
Fix jobconfig no-explicit-any issue (missed logaction)
sbliven Jul 26, 2024
4b290cb
changes in tests:
sofyalaski Jul 29, 2024
819fdd7
address comments in the PR:
sofyalaski Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
pull_request:
branches:
- master
- release-*

jobs:
install-and-cache:
Expand Down Expand Up @@ -152,6 +153,9 @@ jobs:
CREATE_DATASET_WITH_PID_GROUPS: "group2"
CREATE_DATASET_PRIVILEGED_GROUPS: "datasetIngestor,group3"
ACCESS_GROUPS_STATIC_VALUES: "ess"
CREATE_JOB_GROUPS: group1,group2
UPDATE_JOB_GROUPS: group1
DELETE_JOB_GROUPS: "archivemanager"
PROPOSAL_GROUPS: "proposalingestor"
SAMPLE_PRIVILEGED_GROUPS: "sampleingestor"
SAMPLE_GROUPS: "group1"
Expand All @@ -168,6 +172,7 @@ jobs:
STACK_VERSION: 8.8.2
CLUSTER_NAME: es-cluster
MEM_LIMIT: 4G
JOB_CONFIGURATION_FILE: test/config/jobconfig.json

# Start mongo container and app before running api tests
run: |
Expand Down
72 changes: 53 additions & 19 deletions src/casl/casl-ability.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { UserSettings } from "src/users/schemas/user-settings.schema";
import { User } from "src/users/schemas/user.schema";
import { AuthOp } from "./authop.enum";
import configuration from "src/config/configuration";
import { CreateJobAuth, StatusUpdateJobAuth } from "src/jobs/types/jobs-auth.enum";
import {
CreateJobAuth,
StatusUpdateJobAuth,
} from "src/jobs/types/jobs-auth.enum";

type Subjects =
| string
Expand Down Expand Up @@ -818,36 +821,53 @@ export class CaslAbilityFactory {
["configuration.create.auth" as string]: CreateJobAuth.DatasetPublic,
datasetsValidation: true,
});
can(AuthOp.JobStatusUpdateConfiguration, JobClass, {
["configuration.statusUpdate.auth" as string]: StatusUpdateJobAuth.All,
ownerGroup: undefined,
});
} 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
*/

// -------------------------------------
// 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);
} else {
const jobUserAuthorizationValues = [
...user.currentGroups.map((g) => "@" + g),
user.username,
];

if (
user.currentGroups.some((g) =>
configuration().createJobGroups.includes(g),
Expand Down Expand Up @@ -880,7 +900,7 @@ export class CaslAbilityFactory {
];
const jobCreateInstanceAuthorizationValues = [
...Object.values(CreateJobAuth).filter(
(v) => ~String(v).includes("#dataset"),
(v) => !String(v).includes("#dataset"),
),
...jobUserAuthorizationValues,
];
Expand All @@ -889,13 +909,17 @@ export class CaslAbilityFactory {
String(v).includes("#dataset"),
),
];

// -------------------------------------
// endpoint authorization
can(AuthOp.JobRead, JobClass);

if (
configuration().jobConfiguration.some(
(j) => j.create.auth! in jobCreateEndPointAuthorizationValues,
(j) =>
j.create.auth &&
jobCreateEndPointAuthorizationValues.includes(
j.create.auth as string,
),
)
) {
can(AuthOp.JobCreate, JobClass);
Expand All @@ -907,6 +931,7 @@ export class CaslAbilityFactory {
ownerGroup: { $in: user.currentGroups },
ownerUser: user.username,
});

can(AuthOp.JobCreateConfiguration, JobClass, {
["configuration.create.auth" as string]: {
$in: jobCreateInstanceAuthorizationValues,
Expand All @@ -919,6 +944,16 @@ export class CaslAbilityFactory {
datasetsValidation: true,
});
}
const jobUpdateEndPointAuthorizationValues = [
...Object.values(StatusUpdateJobAuth),
...jobUserAuthorizationValues,
];
const jobUpdateInstanceAuthorizationValues = [
...Object.values(StatusUpdateJobAuth).filter(
(v) => !String(v).includes("#job"),
),
...jobUserAuthorizationValues,
];

if (
user.currentGroups.some((g) =>
Expand All @@ -931,29 +966,27 @@ export class CaslAbilityFactory {

// -------------------------------------
// data instance authorization
can(AuthOp.JobStatusUpdateConfiguration, JobClass, {
["configuration.statusUpdate.auth" as string]: {
$in: jobUpdateInstanceAuthorizationValues,
},
});
can(AuthOp.JobStatusUpdateOwner, JobClass, {
ownerUser: user.username,
});
can(AuthOp.JobStatusUpdateOwner, JobClass, {
ownerGroup: { $in: user.currentGroups },
});
} else {
const jobUpdateEndPointAuthorizationValues = [
...Object.values(StatusUpdateJobAuth),
...jobUserAuthorizationValues,
];
const jobUpdateInstanceAuthorizationValues = [
...Object.values(StatusUpdateJobAuth).filter(
(v) => ~String(v).includes("#job"),
),
...jobUserAuthorizationValues,
];

// -------------------------------------
// endpoint authorization
if (
configuration().jobConfiguration.some(
(j) => j.statusUpdate.auth! in jobUpdateEndPointAuthorizationValues,
(j) =>
j.statusUpdate.auth &&
jobUpdateEndPointAuthorizationValues.includes(
j.statusUpdate.auth as string,
),
)
) {
can(AuthOp.JobStatusUpdate, JobClass);
Expand All @@ -975,6 +1008,7 @@ export class CaslAbilityFactory {
ownerGroup: { $in: user.currentGroups },
});
}
cannot(AuthOp.JobDelete, JobClass);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/common/handlebars-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const formatCamelCase = (camelCase: string): string => {
return words;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const jsonify = (context: any): string => {
return JSON.stringify(context, null, 3);
};
6 changes: 4 additions & 2 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Logger } from "@nestjs/common";
import {
loadJobConfig,
registerCreateAction,
Expand Down Expand Up @@ -31,6 +30,7 @@ const configuration = () => {

const createJobGroups = process.env.CREATE_JOB_GROUPS || ("" as string);
const statusUpdateJobGroups = process.env.UPDATE_JOB_GROUPS || ("" as string);
const deleteJobGroups = process.env.DELETE_JOB_GROUPS || ("" as string);

const proposalGroups = process.env.PROPOSAL_GROUPS || ("" as string);
const sampleGroups = process.env.SAMPLE_GROUPS || ("#all" as string);
Expand All @@ -44,7 +44,8 @@ const configuration = () => {
process.env.OIDC_USERINFO_MAPPING_FIELD_USERNAME || ("" as string);

const jobConfigurationFile =
process.env.JOB_CONFIGURATION_FILE || ("src/jobs/config/jobConfig.example.json" as string);
process.env.JOB_CONFIGURATION_FILE ||
("src/jobs/config/jobConfig.example.json" as string);

const defaultLogger = {
type: "DefaultLogger",
Expand Down Expand Up @@ -109,6 +110,7 @@ const configuration = () => {
datasetCreationValidationRegex: datasetCreationValidationRegex,
createJobGroups: createJobGroups,
statusUpdateJobGroups: statusUpdateJobGroups,
deleteJobGroups: deleteJobGroups,
logoutURL: process.env.LOGOUT_URL ?? "", // Example: http://localhost:3000/
accessGroupsGraphQlConfig: {
enabled: boolean(process.env?.ACCESS_GROUPS_GRAPHQL_ENABLED || false),
Expand Down
2 changes: 2 additions & 0 deletions src/jobs/actions/emailaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class EmailJobAction<T> implements JobAction<T> {
return EmailJobAction.actionType;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(data: Record<string, any>) {
Logger.log(
"Initializing EmailJobAction. Params: " + JSON.stringify(data),
Expand Down Expand Up @@ -69,6 +70,7 @@ export class EmailJobAction<T> implements JobAction<T> {
);

// Fill templates
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mail: any = {
to: this.toTemplate(job),
from: this.from,
Expand Down
1 change: 1 addition & 0 deletions src/jobs/actions/logaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class LogJobAction<T> implements JobAction<T> {
Logger.log("Performing job: " + JSON.stringify(job), "LogJobAction");
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(data: Record<string, any>) {
Logger.log(
"Initializing LogJobAction. Params: " + JSON.stringify(data),
Expand Down
74 changes: 48 additions & 26 deletions src/jobs/actions/rabbitmqaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import amqp, { Connection } from "amqplib/callback_api";
import { JobAction } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";


/**
* Publish a message in a RabbitMQ queue
*/
Expand All @@ -12,6 +11,7 @@ export class RabbitMQJobAction<T> implements JobAction<T> {
private connection;
private binding;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(data: Record<string, any>) {
Logger.log(
"Initializing RabbitMQJobAction. Params: " + JSON.stringify(data),
Expand All @@ -28,7 +28,7 @@ export class RabbitMQJobAction<T> implements JobAction<T> {
this.binding = {
exchange: data.exchange,
queue: data.queue,
key: data.key
key: data.key,
};
}

Expand All @@ -42,14 +42,22 @@ export class RabbitMQJobAction<T> implements JobAction<T> {
"RabbitMQJobAction",
);

const connectionDetailsMissing = [undefined, ""].some(el => Object.values(this.connection).includes(el));
const connectionDetailsMissing = [undefined, ""].some((el) =>
Object.values(this.connection).includes(el),
);
if (connectionDetailsMissing) {
throw new NotFoundException("RabbitMQ configuration is missing connection details.");
throw new NotFoundException(
"RabbitMQ configuration is missing connection details.",
);
}

const bindingDetailsMissing = [undefined, ""].some(el => Object.values(this.binding).includes(el));
const bindingDetailsMissing = [undefined, ""].some((el) =>
Object.values(this.binding).includes(el),
);
if (bindingDetailsMissing) {
throw new NotFoundException("RabbitMQ binding is missing exchange/queue/key details.");
throw new NotFoundException(
"RabbitMQ binding is missing exchange/queue/key details.",
);
}
}

Expand All @@ -59,33 +67,47 @@ export class RabbitMQJobAction<T> implements JobAction<T> {
"RabbitMQJobAction",
);

amqp.connect(this.connection, (connectionError: Error, connection: Connection) => {
if (connectionError) {
Logger.error(
"Connection error in RabbitMQJobAction: " + JSON.stringify(connectionError.message),
"RabbitMQJobAction",
);
return;
}

connection.createChannel((channelError: Error, channel) => {
if (channelError) {
amqp.connect(
this.connection,
(connectionError: Error, connection: Connection) => {
if (connectionError) {
Logger.error(
"Channel error in RabbitMQJobAction: " + JSON.stringify(channelError.message),
"Connection error in RabbitMQJobAction: " +
JSON.stringify(connectionError.message),
"RabbitMQJobAction",
);
return;
}

channel.assertQueue(this.binding.queue, { durable: true });
channel.assertExchange(this.binding.exchange, "topic", { durable: true });
channel.bindQueue(this.binding.queue, this.binding.exchange, this.binding.key);
channel.sendToQueue(this.binding.queue, Buffer.from(JSON.stringify(job)));
connection.createChannel((channelError: Error, channel) => {
if (channelError) {
Logger.error(
"Channel error in RabbitMQJobAction: " +
JSON.stringify(channelError.message),
"RabbitMQJobAction",
);
return;
}

channel.close(() => {
connection.close();
channel.assertQueue(this.binding.queue, { durable: true });
channel.assertExchange(this.binding.exchange, "topic", {
durable: true,
});
channel.bindQueue(
this.binding.queue,
this.binding.exchange,
this.binding.key,
);
channel.sendToQueue(
this.binding.queue,
Buffer.from(JSON.stringify(job)),
);

channel.close(() => {
connection.close();
});
});
});
});
},
);
}
}
Loading