diff --git a/ExpressAPI/src/helpers/DojoValidators.ts b/ExpressAPI/src/helpers/DojoValidators.ts index d3a72b56629fe73caeff661d3248f3587c480138..852b164fc3caec9d61a2f635b6ff70bbaf417861 100644 --- a/ExpressAPI/src/helpers/DojoValidators.ts +++ b/ExpressAPI/src/helpers/DojoValidators.ts @@ -7,11 +7,10 @@ import ExerciseResultsFile from '../ import ParamsCallbackManager from '../middlewares/ParamsCallbackManager.js'; import ExerciseManager from '../managers/ExerciseManager.js'; import Toolbox from '../shared/helpers/Toolbox.js'; -import { CustomValidator, FieldMessageFactory, Meta, ValidationChain, ErrorMessage } from 'express-validator/lib/index.js'; -import { ErrorMessage } from 'express-validator/lib/base.js'; -import { StatusCodes } from 'http-status-codes'; -import { BailOptions, ValidationChain } from 'express-validator/src/chain'; -import { Language } from '@prisma/client'; +import { CustomValidator, FieldMessageFactory, Meta, ValidationChain } from 'express-validator/lib/index.js'; +import { Language } from '@prisma/client'; +import { ErrorMessage } from 'express-validator/lib/base'; +import { BailOptions } from 'express-validator/lib/chain'; declare type DojoMeta = Meta & { diff --git a/ExpressAPI/src/managers/SonarManager.ts b/ExpressAPI/src/managers/SonarManager.ts index 38bc133b1bfd81344deec02105cac4120872bceb..4b7a572714f14d9ec6d3966f4ca9b764813877a6 100644 --- a/ExpressAPI/src/managers/SonarManager.ts +++ b/ExpressAPI/src/managers/SonarManager.ts @@ -7,15 +7,15 @@ import https from 'https'; import GlobalHelper from '../helpers/GlobalHelper'; import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; import express from 'express'; -import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; +import * as Gitlab from '@gitbeaker/rest'; class SonarManager { - private instance: AxiosInstance = axios.create({ - httpsAgent: new https.Agent({ - rejectUnauthorized: false - }) - }); + private readonly axiosInstance: AxiosInstance = axios.create({ + httpsAgent: new https.Agent({ + rejectUnauthorized: false + }) + }); private getApiUrl(route: SonarRoute): string { return `${ SharedConfig.sonar.url }${ route }`; @@ -30,9 +30,9 @@ class SonarManager { formData.append('almSetting', 'dojo'); formData.append('pat', Config.gitlab.account.token); - await this.instance.post(this.getApiUrl(SonarRoute.SET_PAT), formData, { + await this.axiosInstance.post(this.getApiUrl(SonarRoute.SET_PAT), formData, { headers: { - Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }` + Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ':') }` } }); } @@ -40,32 +40,31 @@ class SonarManager { private async executePostRequest<T>(url: string, data?: FormData) { await this.setPAT(); // Always set PAT to be sure it has been set - return (await this.instance.post<T>(url, data, { + return (await this.axiosInstance.post<T>(url, data, { headers: { - Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }` + Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ':') }` } })).data; } private async executeGetRequest<T>(url: string, data?: unknown) { - - return (await this.instance.get<T>(url, { + return (await this.axiosInstance.get<T>(url, { headers: { - Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }` + Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ':') }` }, - params: data + params : data })).data; } - async createProjectFromGitlab(projectId: number) { + async createProjectFromGitlab(projectId: number): Promise<SonarProjectCreation> { const formData = new FormData(); formData.append('almSetting', 'dojo'); formData.append('gitlabProjectId', projectId.toString()); - return await this.executePostRequest<SonarProjectCreation>(this.getApiUrl(SonarRoute.PROJECT_CREATE_GITLAB), formData) + return await this.executePostRequest<SonarProjectCreation>(this.getApiUrl(SonarRoute.PROJECT_CREATE_GITLAB), formData); } - async addQualityGate(projectKey: string, qualityGate: string) { + async addQualityGate(projectKey: string, qualityGate: string): Promise<undefined> { const formData = new FormData(); formData.append('projectKey', projectKey); formData.append('gateName', qualityGate); @@ -82,38 +81,25 @@ class SonarManager { return await this.executePostRequest<unknown>(this.getApiUrl(SonarRoute.PROJECT_ADD_PROFILE), formData); } - async createProjectWithQualities(gitlabRepository: GitlabRepository, qualityGate: string | null, qualityProfiles: string[] | null, req: express.Request, res: express.Response) { - let sonarProject: SonarProjectCreation | undefined = undefined; - try { - sonarProject = await this.createProjectFromGitlab(gitlabRepository.id); - if (sonarProject == undefined) { - return await GlobalHelper.repositoryCreationError('Sonar error', undefined, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, gitlabRepository); - } - } catch ( error ) { - return await GlobalHelper.repositoryCreationError('Sonar project creation error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, gitlabRepository, sonarProject); - } + async createProjectWithQualities(gitlabRepository: Gitlab.ProjectSchema, qualityGate: string | null, qualityProfiles: string[] | null, req: express.Request, res: express.Response) { + const repoCreationFnExec = GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, gitlabRepository); + + const sonarProject: SonarProjectCreation = await repoCreationFnExec(() => this.createProjectFromGitlab(gitlabRepository.id), 'Sonar project creation error') as SonarProjectCreation; + // Add gate and profiles to sonar project - if ( qualityGate != undefined && qualityGate != "" ) { - try { - await this.addQualityGate(sonarProject.project.key, qualityGate); - } catch ( error ) { - return await GlobalHelper.repositoryCreationError('Sonar gate error', error, req, res, DojoStatusCode.ASSIGNMENT_SONAR_GATE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_GATE_NOT_FOUND, gitlabRepository, sonarProject); - } + if ( qualityGate != undefined && qualityGate != '' ) { + await repoCreationFnExec(() => this.addQualityGate(sonarProject.project.key, qualityGate), 'Sonar gate error'); } if ( qualityProfiles != undefined && qualityProfiles.length > 0 ) { - for ( const profile of qualityProfiles ) { - try { - const [ lang, name ] = profile.split('/'); - if (lang.trim() != '' && name.trim() != '') { - await this.addQualityProfile(sonarProject.project.key, name.trim(), lang.trim()); - } else { - return await GlobalHelper.repositoryCreationError('Sonar profile invalid', undefined, req, res, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, gitlabRepository, sonarProject); - } - } catch ( error ) { - return await GlobalHelper.repositoryCreationError('Sonar profile not found', error, req, res, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, DojoStatusCode.ASSIGNMENT_SONAR_PROFILE_NOT_FOUND, gitlabRepository, sonarProject); + await repoCreationFnExec(() => Promise.all(qualityProfiles.map(async (profile) => { + const [ lang, name ] = profile.split('/'); + if ( lang.trim() != '' && name.trim() != '' ) { + await this.addQualityProfile(sonarProject.project.key, name.trim(), lang.trim()); + } else { + throw new Error('Sonar profile invalid'); } - } + })), 'Sonar profile not found'); } return sonarProject; @@ -123,12 +109,12 @@ class SonarManager { const formData = new FormData(); formData.append('project', projectKey); - return await this.executePostRequest<SonarProjectCreation>(this.getApiUrl(SonarRoute.PROJECT_DELETE), formData) + return await this.executePostRequest<SonarProjectCreation>(this.getApiUrl(SonarRoute.PROJECT_DELETE), formData); } async getLanguages() { - const resp = await this.executeGetRequest<{ languages: { key: string, name: string }[]}>(this.getApiUrl(SonarRoute.GET_LANGUAGES)) - return resp.languages.map(l => l.key) + const resp = await this.executeGetRequest<{ languages: { key: string, name: string }[] }>(this.getApiUrl(SonarRoute.GET_LANGUAGES)); + return resp.languages.map(l => l.key); } async testQualityGate(gateName: string) { @@ -146,11 +132,9 @@ class SonarManager { formData.append('language', language); formData.append('qualityProfile', profileName); - const resp = await this.executeGetRequest<{ profiles: { key: string, name: string, language: string }[] }>( - this.getApiUrl(SonarRoute.TEST_PROFILE), formData - ); + const resp = await this.executeGetRequest<{ profiles: { key: string, name: string, language: string }[] }>(this.getApiUrl(SonarRoute.TEST_PROFILE), formData); - return (resp.profiles.length > 0 && resp.profiles.some(p => p.name === profileName && p.language === language)) + return (resp.profiles.length > 0 && resp.profiles.some(p => p.name === profileName && p.language === language)); } catch ( e ) { return false; } @@ -165,7 +149,7 @@ class SonarManager { formData.append('q', gitlabUsername); const resp = await this.executeGetRequest<{ users: { login: string, name: string }[] }>(this.getApiUrl(SonarRoute.SEARCH_USER), formData); - for (const u of resp.users) { + for ( const u of resp.users ) { if ( u.name == gitlabUsername ) { return u.login; } @@ -174,24 +158,24 @@ class SonarManager { } async addUserToProject(username: string, projectKey: string, privileged: boolean) { - const permissions = ['user', 'codeviewer']; - if (privileged) { + const permissions = [ 'user', 'codeviewer' ]; + if ( privileged ) { permissions.push('issueadmin'); } - for (const perm of permissions) { + for ( const perm of permissions ) { const formData = new FormData(); formData.append('projectKey', projectKey); formData.append('permission', perm); formData.append('login', username); - await this.executePostRequest(this.getApiUrl(SonarRoute.PROJECT_ADD_USER), formData) + await this.executePostRequest(this.getApiUrl(SonarRoute.PROJECT_ADD_USER), formData); } } async addGitlabUserToProject(gitlabUsername: string, projectKey: string, privileged: boolean) { const username = await this.findUserFromGitlabUser(gitlabUsername); - if (username == undefined) { + if ( username == undefined ) { return false; } await this.addUserToProject(username, projectKey, privileged); @@ -199,4 +183,5 @@ class SonarManager { } } + export default new SonarManager(); \ No newline at end of file diff --git a/ExpressAPI/src/middlewares/SecurityMiddleware.ts b/ExpressAPI/src/middlewares/SecurityMiddleware.ts index 93fdf4a63cb363c5a399d7939a826a0f2c2bac5d..c2e17cfd3aa64a636d97339dd2e5402f13d9f0c7 100644 --- a/ExpressAPI/src/middlewares/SecurityMiddleware.ts +++ b/ExpressAPI/src/middlewares/SecurityMiddleware.ts @@ -28,7 +28,7 @@ class SecurityMiddleware { return req.boundParams.assignment?.published ?? false; case SecurityCheckType.EXERCISE_SECRET.valueOf(): return (req.headers.exercisesecret as string | undefined) === req.boundParams.exercise!.secret; - case SecurityCheckType.ASSIGNMENT_SECRET: + case SecurityCheckType.ASSIGNMENT_SECRET.valueOf(): return (req.headers.assignmentsecret as string | undefined) === req.boundParams.assignment!.secret; default: return false; diff --git a/ExpressAPI/src/routes/AssignmentRoutes.ts b/ExpressAPI/src/routes/AssignmentRoutes.ts index f5f7d225d22743b6704bbead19cd6c1840f145f3..94cf9e655cbe354d76271711d1b6ba9a1c83dc01 100644 --- a/ExpressAPI/src/routes/AssignmentRoutes.ts +++ b/ExpressAPI/src/routes/AssignmentRoutes.ts @@ -31,21 +31,21 @@ import { v4 as uuidv4 } from 'uuid'; class AssignmentRoutes implements RoutesManager { private readonly assignmentValidator: ExpressValidator.Schema = { - name : { + name : { trim : true, notEmpty: true }, - members : { + members : { trim : true, notEmpty : true, customSanitizer: DojoValidators.jsonSanitizer }, - template: { + template : { trim : true, custom : DojoValidators.templateUrlValidator, customSanitizer: DojoValidators.templateUrlSanitizer }, - useSonar: { + useSonar : { trim : true, notEmpty : true, isBoolean: true @@ -55,7 +55,7 @@ class AssignmentRoutes implements RoutesManager { notEmpty : false, isBoolean: true }, - language: { + language : { trim : true, notEmpty: true, custom : DojoValidators.supportedLanguageValidator @@ -201,17 +201,13 @@ class AssignmentRoutes implements RoutesManager { await repoCreationFnExec(() => Promise.all(params.members.map(member => member.id).map(GlobalHelper.addRepoMember(repository.id))), 'Add repository members error'); // Create Sonar project - let sonarProject: SonarProjectCreation | undefined = undefined; + let sonarProject: SonarProjectCreation; if ( useSonar ) { const profiles: string[] = JSON.parse(params.sonarProfiles); sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectWithQualities(repository, params.sonarGate, profiles, req, res), 'Sonar project creation error') as SonarProjectCreation; - if ( sonarProject == undefined ) { - return; - } - await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository, sonarProject)(async () => { - if ( !(await Promise.all(params.members.map(member => SonarManager.addGitlabUserToProject(member.username, sonarProject!.project.key, true)))).every(Boolean) ) { + if ( !(await Promise.all(params.members.map(member => SonarManager.addGitlabUserToProject(member.username, sonarProject.project.key, true)))).every(Boolean) ) { throw new Error(); } }, 'Sonar add member error'); @@ -249,7 +245,7 @@ class AssignmentRoutes implements RoutesManager { } } }), 'Database error') as Assignment; - + req.session.sendResponse(res, StatusCodes.OK, assignment); } catch ( error ) { diff --git a/ExpressAPI/src/routes/BaseRoutes.ts b/ExpressAPI/src/routes/BaseRoutes.ts index 786f3c554227028f1c7fc47c20ed438e1a730df9..19443889404054018c7ce5711a878c491e2c4012 100644 --- a/ExpressAPI/src/routes/BaseRoutes.ts +++ b/ExpressAPI/src/routes/BaseRoutes.ts @@ -3,10 +3,8 @@ import express, { RequestHandler } from 'express'; import { StatusCodes } from 'http-status-codes'; import RoutesManager from '../express/RoutesManager.js'; import Config from '../config/Config'; -import SharedConfig from '../shared/config/SharedConfig'; -import GlobalHelper from '../helpers/GlobalHelper'; -import SharedSonarManager from '../shared/managers/SharedSonarManager'; -import SonarManager from '../managers/SonarManager'; +import SharedSonarManager from '../shared/managers/SharedSonarManager'; +import SonarManager from '../managers/SonarManager'; class BaseRoutes implements RoutesManager { @@ -14,8 +12,8 @@ class BaseRoutes implements RoutesManager { backend.get('/', this.homepage.bind(this) as RequestHandler); backend.get('/health_check', this.healthCheck.bind(this) as RequestHandler); - backend.get('/sonar', this.sonar.bind(this)); - + backend.get('/sonar', this.sonar.bind(this) as RequestHandler); + backend.get('/clients_config', this.clientsConfig.bind(this) as RequestHandler); } @@ -40,7 +38,7 @@ class BaseRoutes implements RoutesManager { private async sonar(req: express.Request, res: express.Response) { const data = { sonarEnabled: await SharedSonarManager.isSonarSupported(), - languages: await SonarManager.getLanguages() + languages : await SonarManager.getLanguages() }; return req.session.sendResponse(res, StatusCodes.OK, data); } diff --git a/ExpressAPI/src/routes/ExerciseRoutes.ts b/ExpressAPI/src/routes/ExerciseRoutes.ts index 77921510c49c3b9d409379273937de2dc6859c73..57a171f6493685665f477d83fd463bc0c3fe66d2 100644 --- a/ExpressAPI/src/routes/ExerciseRoutes.ts +++ b/ExpressAPI/src/routes/ExerciseRoutes.ts @@ -250,7 +250,7 @@ class ExerciseRoutes implements RoutesManager { const exerciseId: string = uuidv4(); const secret: string = uuidv4(); const repository: Gitlab.ProjectSchema | undefined = await this.createExerciseRepository(assignment, params.members, exerciseId, req, res); - let sonarProject: SonarProjectCreation | undefined = undefined; + let sonarProject: SonarProjectCreation; if ( !repository ) { return; @@ -279,28 +279,11 @@ class ExerciseRoutes implements RoutesManager { const profiles: string[] = JSON.parse(assignment.sonarProfiles as string); sonarProject = await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.EXERCISE_CREATION_SONAR_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository)(() => SonarManager.createProjectWithQualities(repository, assignment.sonarGate, profiles, req, res), 'Sonar project creation error') as SonarProjectCreation; - if ( sonarProject == undefined ) { - return; - } - - try { - for ( const u of assignment.staff ) { - const success = await SonarManager.addGitlabUserToProject(u.gitlabUsername, sonarProject.project.key, true); - if (!success) { - return GlobalHelper.repositoryCreationError('Sonar add member error', undefined, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_MEMBER, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_MEMBER, repository, sonarProject); - } + await GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository, sonarProject)(async () => { + if ( !(await Promise.all([ ...params.members.map(member => member.username), ...assignment.staff.map(member => member.gitlabUsername) ].map(username => SonarManager.addGitlabUserToProject(username, sonarProject.project.key, true)))).every(Boolean) ) { + throw new Error(); } - for ( const u of params.members ) { - const success = await SonarManager.addGitlabUserToProject(u.username, sonarProject.project.key, false); - if (!success) { - return GlobalHelper.repositoryCreationError('Sonar add member error', undefined, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_MEMBER, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_MEMBER, repository, sonarProject); - } - } - } catch ( error ) { - logger.error('Sonar add member error'); - logger.error(error); - return GlobalHelper.repositoryCreationError('Sonar add member error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, repository, sonarProject); - } + }, 'Sonar add member error'); } @@ -389,7 +372,7 @@ class ExerciseRoutes implements RoutesManager { private async createResult(req: express.Request, res: express.Response) { const params: { exitCode: number, commit: Record<string, string>, results: ExerciseResultsFile, files: Array<IFileDirStat>, archiveBase64: string, sonarGatePass: string | undefined } = req.body; const exercise: Exercise = req.boundParams.exercise!; - const sonarGate = params.sonarGatePass === "true"; + const sonarGate = params.sonarGatePass === 'true'; const result = await db.result.create({ data: { diff --git a/ExpressAPI/src/routes/SonarRoutes.ts b/ExpressAPI/src/routes/SonarRoutes.ts index 50d7a924ef14eda39b9c7fdae2a07e4e0fb38e69..11efddb72398918793212efdb47195cdd5099852 100644 --- a/ExpressAPI/src/routes/SonarRoutes.ts +++ b/ExpressAPI/src/routes/SonarRoutes.ts @@ -1,14 +1,15 @@ -import { Express } from 'express-serve-static-core'; -import express from 'express'; -import { StatusCodes } from 'http-status-codes'; -import RoutesManager from '../express/RoutesManager'; -import SharedSonarManager from '../shared/managers/SharedSonarManager'; -import SonarManager from '../managers/SonarManager'; -import SecurityMiddleware from '../middlewares/SecurityMiddleware'; -import SecurityCheckType from '../types/SecurityCheckType'; -import * as ExpressValidator from 'express-validator'; -import DojoValidators from '../helpers/DojoValidators'; -import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; +import { Express } from 'express-serve-static-core'; +import express, { RequestHandler } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import RoutesManager from '../express/RoutesManager'; +import SharedSonarManager from '../shared/managers/SharedSonarManager'; +import SonarManager from '../managers/SonarManager'; +import SecurityMiddleware from '../middlewares/SecurityMiddleware'; +import SecurityCheckType from '../types/SecurityCheckType'; +import * as ExpressValidator from 'express-validator'; +import DojoValidators from '../helpers/DojoValidators'; +import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware'; + class SonarRoutes implements RoutesManager { private readonly qualitiesValidator: ExpressValidator.Schema = { @@ -16,7 +17,7 @@ class SonarRoutes implements RoutesManager { trim : true, notEmpty: false }, - profiles : { + profiles: { trim : true, notEmpty : false, customSanitizer: DojoValidators.jsonSanitizer @@ -24,14 +25,14 @@ class SonarRoutes implements RoutesManager { }; registerOnBackend(backend: Express) { - backend.get('/sonar/info', this.sonar.bind(this)); - backend.post('/sonar/testqualities', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.qualitiesValidator), this.testQualities.bind(this)); + backend.get('/sonar/info', this.sonar.bind(this) as RequestHandler); + backend.post('/sonar/testqualities', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.qualitiesValidator), this.testQualities.bind(this) as RequestHandler); } private async sonar(req: express.Request, res: express.Response) { const data = { sonarEnabled: await SharedSonarManager.isSonarSupported(), - languages: await SonarManager.getLanguages() + languages : await SonarManager.getLanguages() }; return req.session.sendResponse(res, StatusCodes.OK, data); } @@ -44,8 +45,8 @@ class SonarRoutes implements RoutesManager { console.log(params); let gateOk = true; - if ((params.gate ?? "") !== "") { - gateOk = await SonarManager.testQualityGate(params.gate ?? "") + if ( (params.gate ?? '') !== '' ) { + gateOk = await SonarManager.testQualityGate(params.gate ?? ''); } let profilesOk = true; @@ -58,7 +59,7 @@ class SonarRoutes implements RoutesManager { profilesOk = false; badProfiles.push(profile); } - } catch (e) { + } catch ( e ) { profilesOk = false; badProfiles.push(profile); } @@ -67,9 +68,9 @@ class SonarRoutes implements RoutesManager { console.log(gateOk, profilesOk); const data = { - valid: gateOk && profilesOk, + valid : gateOk && profilesOk, badProfiles: badProfiles, - badGate: (gateOk ? null : params.gate) + badGate : (gateOk ? null : params.gate) }; console.log(data);