Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • add_route_assignments
  • ask-user-to-delete-exercises-on-duplicates
  • bedran_exercise-list
  • jw_sonar
  • jw_sonar_backup
  • main
  • update-dependencies
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
  • add_route_assignments
  • ask-user-to-delete-exercises-on-duplicates
  • bedran_exercise-list
  • jw_sonar
  • jw_sonar_backup
  • main
  • update-dependencies
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.1.3
  • 3.2.0
  • 3.3.0
  • 3.4.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 3.5.1
  • 3.5.2
  • 3.5.3
  • 4.0.0
  • 4.1.0
  • 5.0.0
  • 5.0.1
  • 6.0.0-dev
  • v1.0.1
32 results
Show changes
Commits on Source (9)
Showing
with 106 additions and 223 deletions
-- AlterTable
ALTER TABLE Enonce
ADD COLUMN published BOOLEAN NOT NULL DEFAULT FALSE;
......@@ -28,6 +28,7 @@ model Enonce {
gitlabCreationInfo Json @db.Json
gitlabLastInfo Json @db.Json
gitlabLastInfoDate DateTime
published Boolean @default(false)
exercices Exercice[]
staff User[]
......
class Config {
private static _instance: Config;
public readonly api: {
port: number
};
......@@ -28,7 +26,7 @@ class Config {
public readonly userPasswordLength: number;
public readonly userPasswordSaltRounds: number;
private constructor() {
constructor() {
this.api = {
port: Number(process.env.API_PORT)
};
......@@ -82,15 +80,7 @@ class Config {
}
public static get instance(): Config {
if ( !Config._instance ) {
Config._instance = new Config();
}
return Config._instance;
}
}
export default Config.instance;
export default new Config();
......@@ -14,18 +14,6 @@ declare type DojoCustomValidator = (input: any, meta: DojoMeta) => any;
class DojoValidators {
private static _instance: DojoValidators;
private constructor() { }
public static get instance(): DojoValidators {
if ( !DojoValidators._instance ) {
DojoValidators._instance = new DojoValidators();
}
return DojoValidators._instance;
}
private toValidatorSchemaOptions(arg: { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: DojoCustomValidator | ValidationChain, options?: DojoCustomValidator }) {
// This is a hack to make the types work with req arg as ApiRequest instead of Request
return arg as unknown as { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: CustomValidator | ValidationChain, options?: CustomValidator };
......@@ -94,4 +82,4 @@ class DojoValidators {
}
export default DojoValidators.instance;
\ No newline at end of file
export default new DojoValidators();
\ No newline at end of file
import { Prisma } from '@prisma/client';
import { Enonce } from '../types/DatabaseTypes';
import { Enonce, User } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper';
class EnonceManager {
private static _instance: EnonceManager;
private constructor() { }
public static get instance(): EnonceManager {
if ( !EnonceManager._instance ) {
EnonceManager._instance = new EnonceManager();
async isUserAllowedToAccessEnonce(enonce: Enonce, user: User): Promise<boolean> {
if ( !enonce.staff ) {
enonce.staff = await db.enonce.findUnique({
where: {
name: enonce.name
}
return EnonceManager._instance;
}).staff();
}
return enonce.staff.findIndex(staff => staff.id === user.id) !== -1;
}
async getByName(name: string, include: Prisma.EnonceInclude | undefined = undefined): Promise<Enonce | undefined> {
......@@ -38,4 +37,4 @@ class EnonceManager {
}
export default EnonceManager.instance;
export default new EnonceManager();
......@@ -11,18 +11,6 @@ import GitlabRoutes from '../shared/types/Gitlab/GitlabRoutes';
class GitlabManager {
private static _instance: GitlabManager;
private constructor() { }
public static get instance(): GitlabManager {
if ( !GitlabManager._instance ) {
GitlabManager._instance = new GitlabManager();
}
return GitlabManager._instance;
}
private getApiUrl(route: GitlabRoutes): string {
return `${ Config.gitlab.apiURL }${ route }`;
}
......@@ -84,6 +72,16 @@ class GitlabManager {
return response.data;
}
async editRepository(repoId: number, newAttributes: Partial<GitlabRepository>): Promise<GitlabRepository> {
const response = await axios.put<GitlabRepository>(this.getApiUrl(GitlabRoutes.REPOSITORY_EDIT).replace('{{id}}', String(repoId)), newAttributes);
return response.data;
}
async changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<GitlabRepository> {
return await this.editRepository(repoId, { visibility: visibility.toString() });
}
async addRepositoryMember(repoId: number, userId: number, accessLevel: GitlabAccessLevel): Promise<GitlabMember> {
const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoutes.REPOSITORY_MEMBER_ADD).replace('{{id}}', String(repoId)), {
user_id : userId,
......@@ -98,7 +96,7 @@ class GitlabManager {
try {
const project: GitlabRepository = await this.getRepository(idOrNamespace);
if ( [ GitlabVisibility.Public.valueOf(), GitlabVisibility.Internal.valueOf() ].includes(project.visibility) ) {
if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) {
return StatusCodes.OK;
}
} catch ( e ) {
......@@ -126,4 +124,4 @@ class GitlabManager {
}
export default GitlabManager.instance;
export default new GitlabManager();
......@@ -5,20 +5,6 @@ import logger from '../shared/logging/Winsto
class HttpManager {
public handleCommandErrors: boolean = true;
private static _instance: HttpManager;
public static get instance(): HttpManager {
if ( !HttpManager._instance ) {
HttpManager._instance = new HttpManager();
}
return HttpManager._instance;
}
private constructor() { }
registerAxiosInterceptor() {
this.registerRequestInterceptor();
this.registerResponseInterceptor();
......@@ -54,5 +40,5 @@ class HttpManager {
}
export default HttpManager.instance;
export default new HttpManager();
......@@ -5,18 +5,6 @@ import { User } from '../types/DatabaseTypes';
class UserManager {
private static _instance: UserManager;
private constructor() { }
public static get instance(): UserManager {
if ( !UserManager._instance ) {
UserManager._instance = new UserManager();
}
return UserManager._instance;
}
async getByMail(mail: string, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
return db.user.findUnique({
where : {
......@@ -65,4 +53,4 @@ class UserManager {
}
export default UserManager.instance;
export default new UserManager();
......@@ -6,18 +6,6 @@ import EnonceManager from '../managers/EnonceManager';
class ParamsCallbackManager {
private static _instance: ParamsCallbackManager;
private constructor() { }
public static get instance(): ParamsCallbackManager {
if ( !ParamsCallbackManager._instance ) {
ParamsCallbackManager._instance = new ParamsCallbackManager();
}
return ParamsCallbackManager._instance;
}
protected listenParam(paramName: string, backend: Express, getFunction: (id: string | number, ...args: Array<any>) => Promise<any>, args: Array<any>, indexName: string) {
backend.param(paramName, (req: ApiRequest, res: express.Response, next: express.NextFunction, id: string | number) => {
getFunction(id, ...args).then(result => {
......@@ -42,17 +30,12 @@ class ParamsCallbackManager {
}
register(backend: Express) {
this.listenParam('enonceNameOrUrl',
backend,
EnonceManager.get.bind(EnonceManager),
[ {
this.listenParam('enonceNameOrUrl', backend, EnonceManager.get.bind(EnonceManager), [ {
exercices: true,
staff : true
} ],
'enonce'
);
} ], 'enonce');
}
}
export default ParamsCallbackManager.instance;
export default new ParamsCallbackManager();
......@@ -5,18 +5,6 @@ import ApiRequest from '../types/ApiRequest';
class ParamsValidatorMiddleware {
private static _instance: ParamsValidatorMiddleware;
private constructor() { }
public static get instance(): ParamsValidatorMiddleware {
if ( !ParamsValidatorMiddleware._instance ) {
ParamsValidatorMiddleware._instance = new ParamsValidatorMiddleware();
}
return ParamsValidatorMiddleware._instance;
}
validate(validations: Array<ExpressValidator.ValidationChain> | ExpressValidator.Schema): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void {
return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => {
......@@ -37,4 +25,4 @@ class ParamsValidatorMiddleware {
}
export default ParamsValidatorMiddleware.instance;
export default new ParamsValidatorMiddleware();
......@@ -3,21 +3,10 @@ import { StatusCodes } from 'http-status-codes';
import SecurityCheckType from '../types/SecurityCheckType';
import logger from '../shared/logging/WinstonLogger';
import ApiRequest from '../types/ApiRequest';
import EnonceManager from '../managers/EnonceManager';
class SecurityMiddleware {
private static _instance: SecurityMiddleware;
private constructor() { }
public static get instance(): SecurityMiddleware {
if ( !SecurityMiddleware._instance ) {
SecurityMiddleware._instance = new SecurityMiddleware();
}
return SecurityMiddleware._instance;
}
// First check if connected then check if at least ONE rule match. It's NOT an AND but it's a OR function.
check(checkIfConnected: boolean, ...checkTypes: Array<SecurityCheckType>): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void {
return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => {
......@@ -36,6 +25,12 @@ class SecurityMiddleware {
case SecurityCheckType.TEACHING_STAFF:
isAllowed = isAllowed || req.session.profile.isTeachingStaff;
break;
case SecurityCheckType.ENONCE_STAFF:
isAllowed = isAllowed || await EnonceManager.isUserAllowedToAccessEnonce(req.boundParams.enonce, req.session.profile);
break;
case SecurityCheckType.ENONCE_IS_PUBLISHED:
isAllowed = isAllowed || req.boundParams.enonce.published;
break;
default:
isAllowed = isAllowed || false;
break;
......@@ -57,4 +52,4 @@ class SecurityMiddleware {
}
export default SecurityMiddleware.instance;
export default new SecurityMiddleware();
......@@ -4,18 +4,6 @@ import Session from '../controllers/Session';
class SessionMiddleware {
private static _instance: SessionMiddleware;
private constructor() { }
public static get instance(): SessionMiddleware {
if ( !SessionMiddleware._instance ) {
SessionMiddleware._instance = new SessionMiddleware();
}
return SessionMiddleware._instance;
}
register(): (req: ApiRequest, res: express.Response, next: express.NextFunction) => void {
return async (req: ApiRequest, res: express.Response, next: express.NextFunction) => {
req.session = new Session();
......@@ -27,4 +15,4 @@ class SessionMiddleware {
}
export default SessionMiddleware.instance;
export default new SessionMiddleware();
......@@ -8,18 +8,6 @@ import ExerciceRoutes from './ExerciceRoutes';
class AdminRoutesManager implements RoutesManager {
private static _instance: AdminRoutesManager;
private constructor() { }
public static get instance(): AdminRoutesManager {
if ( !AdminRoutesManager._instance ) {
AdminRoutesManager._instance = new AdminRoutesManager();
}
return AdminRoutesManager._instance;
}
registerOnBackend(backend: Express) {
BaseRoutes.registerOnBackend(backend);
SessionRoutes.registerOnBackend(backend);
......@@ -30,4 +18,4 @@ class AdminRoutesManager implements RoutesManager {
}
export default AdminRoutesManager.instance;
export default new AdminRoutesManager();
......@@ -6,18 +6,6 @@ import RoutesManager from '../express/RoutesManager';
class BaseRoutes implements RoutesManager {
private static _instance: BaseRoutes;
private constructor() { }
public static get instance(): BaseRoutes {
if ( !BaseRoutes._instance ) {
BaseRoutes._instance = new BaseRoutes();
}
return BaseRoutes._instance;
}
registerOnBackend(backend: Express) {
backend.get('/', (req: ApiRequest, res: express.Response) => { res.status(StatusCodes.OK).end(); });
backend.get('/health_check', (req: ApiRequest, res: express.Response) => { res.status(StatusCodes.OK).end(); });
......@@ -25,4 +13,4 @@ class BaseRoutes implements RoutesManager {
}
export default BaseRoutes.instance;
export default new BaseRoutes();
......@@ -19,21 +19,11 @@ import DojoValidators from '../helpers/DojoValidators';
import { Prisma } from '@prisma/client';
import db from '../helpers/DatabaseHelper';
import { Enonce } from '../types/DatabaseTypes';
import EnonceManager from '../managers/EnonceManager';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
class EnonceRoutes implements RoutesManager {
private static _instance: EnonceRoutes;
private constructor() { }
public static get instance(): EnonceRoutes {
if ( !EnonceRoutes._instance ) {
EnonceRoutes._instance = new EnonceRoutes();
}
return EnonceRoutes._instance;
}
private readonly enonceValidator: ExpressValidator.Schema = {
name : {
trim : true,
......@@ -54,11 +44,26 @@ class EnonceRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.get('/enonces/:enonceNameOrUrl', SecurityMiddleware.check(true), this.getEnonce);
backend.post('/enonces', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.enonceValidator), this.createEnonce);
backend.patch('/enonces/:enonceNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(true));
backend.patch('/enonces/:enonceNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_STAFF), this.changeEnoncePublishedStatus(false));
}
// Get an enonce by its name or gitlab url
private async getEnonce(req: ApiRequest, res: express.Response) {
return req.boundParams.enonce ? req.session.sendResponse(res, StatusCodes.OK, req.boundParams.enonce) : res.status(StatusCodes.NOT_FOUND).send();
const enonce: Enonce = req.boundParams.enonce;
if ( enonce && !enonce.published && !await EnonceManager.isUserAllowedToAccessEnonce(enonce, req.session.profile) ) {
delete enonce.gitlabId;
delete enonce.gitlabLink;
delete enonce.gitlabCreationInfo;
delete enonce.gitlabLastInfo;
delete enonce.gitlabLastInfoDate;
delete enonce.staff;
delete enonce.exercices;
}
return enonce ? req.session.sendResponse(res, StatusCodes.OK, enonce) : res.status(StatusCodes.NOT_FOUND).send();
}
private async createEnonce(req: ApiRequest, res: express.Response) {
......@@ -126,7 +131,35 @@ class EnonceRoutes implements RoutesManager {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
}
}
private changeEnoncePublishedStatus(publish: boolean): (req: ApiRequest, res: express.Response) => Promise<void> {
return async (req: ApiRequest, res: express.Response): Promise<void> => {
try {
await GitlabManager.changeRepositoryVisibility(req.boundParams.enonce.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);
await db.enonce.update({
where: {
name: req.boundParams.enonce.name
},
data : {
published: publish
}
});
req.session.sendResponse(res, StatusCodes.OK);
} catch ( error ) {
if ( error instanceof AxiosError ) {
res.status(error.response.status).send();
return;
}
logger.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
}
};
}
}
export default EnonceRoutes.instance;
export default new EnonceRoutes();
......@@ -19,21 +19,10 @@ import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel'
import { Prisma } from '@prisma/client';
import { Enonce, Exercice } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper';
import SecurityCheckType from '../types/SecurityCheckType';
class ExerciceRoutes implements RoutesManager {
private static _instance: ExerciceRoutes;
private constructor() { }
public static get instance(): ExerciceRoutes {
if ( !ExerciceRoutes._instance ) {
ExerciceRoutes._instance = new ExerciceRoutes();
}
return ExerciceRoutes._instance;
}
private readonly exerciceValidator: ExpressValidator.Schema = {
members: {
trim : true,
......@@ -43,7 +32,7 @@ class ExerciceRoutes implements RoutesManager {
};
registerOnBackend(backend: Express) {
backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this));
backend.post('/enonces/:enonceNameOrUrl/exercices', SecurityMiddleware.check(true, SecurityCheckType.ENONCE_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciceValidator), this.createExercice.bind(this));
}
private getExerciceName(enonce: Enonce, members: Array<GitlabUser>, suffix: number): string {
......@@ -133,4 +122,4 @@ class ExerciceRoutes implements RoutesManager {
}
export default ExerciceRoutes.instance;
export default new ExerciceRoutes();
......@@ -8,18 +8,6 @@ import GitlabManager from '../managers/GitlabManager';
class EnonceRoutes implements RoutesManager {
private static _instance: EnonceRoutes;
private constructor() { }
public static get instance(): EnonceRoutes {
if ( !EnonceRoutes._instance ) {
EnonceRoutes._instance = new EnonceRoutes();
}
return EnonceRoutes._instance;
}
registerOnBackend(backend: Express) {
backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess);
}
......@@ -32,4 +20,4 @@ class EnonceRoutes implements RoutesManager {
}
export default EnonceRoutes.instance;
export default new EnonceRoutes();
......@@ -12,18 +12,6 @@ import { User } from '../types/DatabaseTypes';
class SessionRoutes implements RoutesManager {
private static _instance: SessionRoutes;
private constructor() { }
public static get instance(): SessionRoutes {
if ( !SessionRoutes._instance ) {
SessionRoutes._instance = new SessionRoutes();
}
return SessionRoutes._instance;
}
private readonly loginValidator: ExpressValidator.Schema = {
user : {
trim : true,
......@@ -59,4 +47,4 @@ class SessionRoutes implements RoutesManager {
}
export default SessionRoutes.instance;
export default new SessionRoutes();
Subproject commit 0328c67fd0cade4b51fbf82afc7a07a7d16abe5d
Subproject commit 69905ce759cb3edaf7f78ef5cc815d199ae325b9
enum SecurityCheckType {
TEACHING_STAFF = 'teachingStaff',
ENONCE_STAFF = 'enonceStaff',
ENONCE_IS_PUBLISHED = 'enonceIsPublished',
}
......