Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions controllers/_common/utlis_fct.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,9 @@ module.exports = {
.bulkCreate(
really_new_tags.map(tag => {
return {
// no matter of the kind of user, creating tags like that should be reviewed
state: tagState.PENDING,
// by default, creating tags like that should be reviewed
// (except if it is created by an admin)
state: (tag.hasOwnProperty("state")) ? tag.state : tagState.PENDING,
text: tag.text,
category_id: tag.category_id,
// some timestamps must be inserted
Expand Down Expand Up @@ -329,8 +330,9 @@ function store_single_exercise(user, exercise_data, existent_tags, really_new_ta
.bulkCreate(
really_new_tags.map(tag => {
return {
// no matter of the kind of user, creating tags like that should be reviewed
state: tagState.PENDING,
// by default, creating tags like that should be reviewed
// (except if it is created by an admin)
state: (tag.hasOwnProperty("state")) ? tag.state : tagState.PENDING,
text: tag.text,
category_id: tag.category_id,
// some timestamps must be inserted
Expand Down
5 changes: 3 additions & 2 deletions controllers/exercises/updateExercise.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ function insert_new_tags_if_there_is_at_least_one(tags_to_be_inserted, t) {
return models
.Tag
.bulkCreate(tags_to_be_inserted.map(tag => ({
// no matter of the kind of user, creating tags like that should be reviewed
state: tagState.NOT_VALIDATED,
// by default, creating tags like that should be reviewed
// (except if it is created by an admin)
state: (tag.hasOwnProperty("state")) ? tag.state : tagState.PENDING,
text: tag.text,
category_id: tag.category_id,
// some timestamps must be inserted
Expand Down
23 changes: 22 additions & 1 deletion middlewares/rules/bulk.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const chain = require('connect-chain-if');
const {check_credentials_on_exercises} = require("../../controllers/_common/utlis_fct");

const {pass_middleware, check_exercise_state} = require("./common_sub_middlewares");
const {pass_middleware, check_exercise_state, check_tags_state} = require("./common_sub_middlewares");
const check_user_role = require("../check_user_role");
const {USERS} = require("../../controllers/_common/constants");

Expand Down Expand Up @@ -46,6 +46,27 @@ module.exports = (operation) => (req, res, next) => {
),
pass_middleware
),
// If endpoint === createMultipleExercises , we should check the state given in exercise tags
chain.if(
operation["x-operation"] === "createMultipleExercises",
// as this endpoint use 2 different schema, extraction is a little different
check_tags_state(
Array
.from(
(req.is("json"))
? req.body
: req.body.exercisesData,
// Only takes tags objects
ex => ex.tags
)
// if no tags were given
.filter(s => s !== undefined)
// Only takes tags objects
.filter(tags => tags.filter(tag => isNaN(tag)))
// reduce result to an array of dimension 1 (needed for middelware)
.reduce((acc, val) => acc.concat(val), [])
)
),
// If endpoint === createMultipleTags , we should check if the user is authorized to include "state" property
chain.if(
operation["x-operation"] === "createMultipleTags",
Expand Down
25 changes: 21 additions & 4 deletions middlewares/rules/common_sub_middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,39 @@ const {USERS, TAGS} = require("../../../controllers/_common/constants");
const not_allowed_for_user = [TAGS.VALIDATED, TAGS.NOT_VALIDATED];
const authorizedUsers = [USERS.ADMIN, USERS.SUPER_ADMIN];

// Create an Forbidden message
function create_forbidden_error() {
let error = new Error("FORBIDDEN");
error.message = "It seems you tried to set a state reserved for admin : " +
"This incident will be reported";
error.status = 403;
return error;
}

module.exports = {
// check if states given are allowed in this case
check_exercise_state: function (states) {
return (_req, _res, _next) => {
/* istanbul ignore next */
if (!authorizedUsers.includes(_req.user.role) && states.some(state => not_allowed_for_user.includes(state))) {
let error = new Error("FORBIDDEN");
error.message = "It seems you tried to set a state reserved for admin : " +
"This incident will be reported";
error.status = 403;
let error = create_forbidden_error();
_next(error);
} else {
_next()
}
};
},
// check if given tag(s) are allowed in this case
check_tags_state: function(tags) {
return (_req, _res, _next) => {
if (!authorizedUsers.includes(_req.user.role) && tags.some(tag => tag.hasOwnProperty("state")) ) {
let error = create_forbidden_error();
_next(error);
} else {
_next()
}
}
},
// Nothing to do
pass_middleware: function (req, res, next) {
next();
Expand Down
11 changes: 9 additions & 2 deletions middlewares/rules/exercises.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const chain = require('connect-chain-if');
const {check_credentials_on_exercises, validated_tag_count} = require("../../controllers/_common/utlis_fct");
const {pass_middleware, check_exercise_state} = require("./common_sub_middlewares");
const {pass_middleware, check_exercise_state, check_tags_state} = require("./common_sub_middlewares");

// Arrays for check
const check_credentials_endpoints = ["UpdateExercise", "createSingleExercise"];
Expand All @@ -27,7 +27,14 @@ module.exports = (operation) => (req, res, next) => {
check_exercise_state([req.body.state].filter(s => s !== undefined)),
pass_middleware
),
// Third check that user have add at least 3 validated tags
// Third check that user is allowed to use stats for tag(s)
chain.if(
check_credentials_endpoints.includes(operation["x-operation"]),
// to deal with the fact this security middelware deal with other endpoint that might not have this property
check_tags_state( (req.body.tags || []).filter(tag => isNaN(tag))),
pass_middleware
),
// Fourth check that user have add at least 3 validated tags
chain.if(
check_credentials_endpoints.includes(operation["x-operation"]),
(_req, _res, _next) => {
Expand Down
12 changes: 10 additions & 2 deletions openapi/definitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ components:
required:
- text
- category_id
# TagProposal with state (only for admin)
TagProposalWithState:
allOf:
- $ref: "#/components/schemas/TagProposal"
- type: object
properties:
state:
$ref: "#/components/schemas/TagState"
# Tag updated / validated
TagFull:
allOf:
Expand Down Expand Up @@ -329,8 +337,8 @@ components:
- type: integer
minimum: 0
description: "A Tag ID ( already existent in database )"
- $ref: "#/components/schemas/TagProposal"
description: "A not-existent Tag we want to add"
- $ref: "#/components/schemas/TagProposalWithState"
description: "A not-existent Tag with state we want to add"
description: "Mixed array that contains existent tag(s) or not"
uniqueItems: true
minItems: 3 # We must always put at least N tag(s) in the database
Expand Down
46 changes: 42 additions & 4 deletions tests/endpoints.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ describe("Complex scenarios", () => {
tags: some_tags_ids.concat(
["SOME_TAG1", "SOME_TAG2", "SOME_TAG3", "some_Tag3"].map(tag => ({
text: tag,
category_id: tag_categories_ids[0]
category_id: tag_categories_ids[0],
state: "PENDING"
}))
),
"state": "DRAFT"
Expand All @@ -479,6 +480,7 @@ describe("Complex scenarios", () => {
let data = response.data[0];
expect(data.version).toBe(0);
expect(data.state).toBe("DRAFT");
expect(data.tags.some(tag => tag.state === "PENDING")).toBe(true);

// A simple user should not be able to delete exercises
await request
Expand Down Expand Up @@ -1050,6 +1052,7 @@ describe("Using multipart/form-data (instead of JSON)", () => {
.field("tags[1][category_id]", 1)
.field("tags[2][text]", "MULTI PART exercise 3")
.field("tags[2][category_id]", 1)
.field("tags[2][state]", "DEPRECATED")
.field("tags[3]", 1)
.field("tags[4]", 2)
.field("tags[5]", 3);
Expand All @@ -1058,6 +1061,10 @@ describe("Using multipart/form-data (instead of JSON)", () => {
const exercise = await search_exercise(1, search_criteria);
expect(exercise.data[0].file).not.toBe(null);
expect(exercise.data[0].url).not.toBe(null);
// See if we can find the deprecated tag
expect(exercise.data[0].tags
.some(tag => tag["tag_text"] === "MULTI PART exercise 3" && tag.state === "DEPRECATED")
).toBe(true);

responseTmp = await request
.put("/api/exercises/" + exercise.data[0].id)
Expand All @@ -1072,20 +1079,32 @@ describe("Using multipart/form-data (instead of JSON)", () => {
.field("tags[0][category_id]", 1)
.field("tags[1][text]", "MULTI PART exercise 2")
.field("tags[1][category_id]", 1)
// This time, let's see if the system had recognized the similar tags (including the one with no state set)
.field("tags[2][text]", "MULTI PART exercise 3")
.field("tags[2][category_id]", 1)
.field("tags[3]", 1)
.field("tags[4]", 2)
.field("tags[5]", 3);
.field("tags[5]", 3)
// Add a new tag with state ( to trigger validation & testing )
.field("tags[6][text]", "MULTI PART exercise 42")
.field("tags[6][category_id]", 1)
.field("tags[6][state]", "NOT_VALIDATED");
expect(responseTmp.status).toBe(200);

const exercise2 = await search_exercise(1, search_criteria);
expect(exercise2.data[0].file).not.toBe(exercise.data[0].file);
expect(exercise2.data[0].url).toBe(exercise2.data[0].url);
expect(exercise2.data[0].title).toBe(title);
expect(exercise2.data[0].description).toBe("Something changes ...");
expect(exercise2.data[0].tags).toHaveLength(exercise.data[0].tags.length);

expect(exercise2.data[0].tags).toHaveLength(exercise.data[0].tags.length + 1);
// This time, let's see if the system had recognized the similar tags (including the one with no state set)
expect(exercise2.data[0].tags
.some(tag => tag["tag_text"] === "MULTI PART exercise 3" && tag.state === "DEPRECATED")
).toBe(true);
// And also find the NOT_VALIDATED tag
expect(exercise2.data[0].tags
.some(tag => tag["tag_text"] === "MULTI PART exercise 42" && tag.state === "NOT_VALIDATED")
).toBe(true);
});
it("Should be able to upload multiple exercises with their linked files then delete one of them", async () => {

Expand Down Expand Up @@ -1284,6 +1303,25 @@ describe("Validations testing", () => {
expect(responseTemp.status).toBe(403);
});

it("POST /api/create_exercise : An simple user cannot insert a exercise with tags state", async () => {
// creates one exercise
const some_exercise_data = {
"title": "HELLO WORLD",
"description": "Some verrrrrrrrrry long description here",
tags: ["SOME_TAG1", "SOME_TAG2", "SOME_TAG3"].map(text => ({
text: text,
category_id: 42,
state: "VALIDATED"
}))
};
let responseTemp = await request
.post("/api/create_exercise")
.set('Authorization', 'bearer ' + JWT_TOKEN_2)
.set('Content-Type', 'application/json')
.send(some_exercise_data);
expect(responseTemp.status).toBe(403);
});

it("POST /api/ : Cannot create/update an exercise with not expected validated tag count", async () => {
// creates one exercise with 2
const title = "HELLO WORLD";
Expand Down