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

Target

Select target project
  • Dojo_Project_Nguyen/backend/dojobackendapi
  • dojo_project/projects/backend/dojobackendapi
2 results
Select Git revision
Show changes
Showing
with 1162 additions and 575 deletions
import axios from 'axios';
import Config from '../config/Config';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import GitlabMember from '../shared/types/Gitlab/GitlabMember';
import Config from '../config/Config.js';
import { StatusCodes } from 'http-status-codes';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile';
import parseLinkHeader from 'parse-link-header';
import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility.js';
import express from 'express';
import GitlabRoute from '../shared/types/Gitlab/GitlabRoute';
import SharedConfig from '../shared/config/SharedConfig';
import GitlabProfile from '../shared/types/Gitlab/GitlabProfile';
import GitlabRelease from '../shared/types/Gitlab/GitlabRelease';
import { CommitSchema, Gitlab } from '@gitbeaker/rest';
import logger from '../shared/logging/WinstonLogger';
class GitlabManager {
readonly api = new Gitlab({
host : SharedConfig.gitlab.URL,
token: Config.gitlab.account.token
});
import { CommitSchema, ExpandedUserSchema, Gitlab, MemberSchema, ProjectBadgeSchema, ProjectSchema, ReleaseSchema, RepositoryFileExpandedSchema, RepositoryFileSchema, RepositoryTreeSchema } from '@gitbeaker/rest';
import logger from '../shared/logging/WinstonLogger.js';
import { AccessLevel, EditProjectOptions, ProjectVariableSchema, ProtectedBranchAccessLevel, ProtectedBranchSchema } from '@gitbeaker/core';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js';
import SharedGitlabManager from '../shared/managers/SharedGitlabManager.js';
private getApiUrl(route: GitlabRoute): string {
return `${ SharedConfig.gitlab.apiURL }${ route }`;
}
public async getUserProfile(token: string): Promise<GitlabProfile | undefined> {
try {
return (await axios.get<GitlabProfile>(this.getApiUrl(GitlabRoute.PROFILE_GET), {
headers: {
DojoOverrideAuthorization: true,
DojoAuthorizationHeader : 'Authorization',
DojoAuthorizationValue : `Bearer ${ token }`
}
})).data;
} catch ( e ) {
return undefined;
}
class GitlabManager extends SharedGitlabManager {
constructor() {
super(Config.gitlab.url, Config.gitlab.account.token);
}
public async getUserById(id: number): Promise<GitlabUser | undefined> {
getUserProfile(token: string): Promise<ExpandedUserSchema> | undefined {
try {
const user = (await axios.get<GitlabUser>(`${ this.getApiUrl(GitlabRoute.USERS_GET) }/${ String(id) }`)).data;
const profileApi = new Gitlab({
host : Config.gitlab.url,
oauthToken: token
});
return user.id === id ? user : undefined;
return profileApi.Users.showCurrentUser();
} catch ( e ) {
logger.error(JSON.stringify(e));
return undefined;
}
}
public async getUserByUsername(username: string): Promise<GitlabUser | undefined> {
async getRepositoryMembers(idOrNamespace: string, includeInherited: boolean = true): Promise<Array<MemberSchema>> {
try {
const params: Record<string, string> = {};
params['search'] = username;
const user = (await axios.get<Array<GitlabUser>>(this.getApiUrl(GitlabRoute.USERS_GET), { params: params })).data[0];
return user.username === username ? user : undefined;
return await this.api.ProjectMembers.all(idOrNamespace, { includeInherited: includeInherited });
} catch ( e ) {
return undefined;
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async getRepository(projectIdOrNamespace: string): Promise<GitlabRepository> {
const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(projectIdOrNamespace)));
return response.data;
}
async getRepositoryMembers(idOrNamespace: string): Promise<Array<GitlabMember>> {
const response = await axios.get<Array<GitlabMember>>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBERS_GET).replace('{{id}}', encodeURIComponent(idOrNamespace)));
return response.data;
async getRepositoryReleases(repoId: number): Promise<Array<ReleaseSchema>> {
try {
return await this.api.ProjectReleases.all(repoId);
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
async getRepositoryReleases(repoId: number): Promise<Array<GitlabRelease>> {
const response = await axios.get<Array<GitlabRelease>>(this.getApiUrl(GitlabRoute.REPOSITORY_RELEASES_GET).replace('{{id}}', String(repoId)));
return response.data;
}
async getRepositoryLastCommit(repoId: number, branch: string = 'main'): Promise<CommitSchema | undefined> {
......@@ -92,93 +56,152 @@ class GitlabManager {
return commits.length > 0 ? commits[0] : undefined;
} catch ( e ) {
logger.error(e);
logger.error(JSON.stringify(e));
return undefined;
}
}
async getRepositoryCommit(repoId: number, commitSha: string): Promise<CommitSchema | undefined> {
try {
return await this.api.Commits.show(repoId, commitSha);
} catch ( e ) {
logger.error(JSON.stringify(e));
return undefined;
}
}
async createRepository(name: string, description: string, visibility: string, initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, import_url: string): Promise<GitlabRepository> {
const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_CREATE), {
async createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<ProjectSchema> {
try {
return await this.api.Projects.create({
name : name,
description : description,
import_url : import_url,
initialize_with_readme: initializeWithReadme,
namespace_id : namespace,
shared_runners_enabled: sharedRunnersEnabled,
importUrl : importUrl,
initializeWithReadme: initializeWithReadme,
namespaceId : namespace,
sharedRunnersEnabled: sharedRunnersEnabled,
visibility : visibility,
wiki_enabled : wikiEnabled
wikiAccessLevel : wikiEnabled ? 'enabled' : 'disabled'
});
return response.data;
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async deleteRepository(repoId: number): Promise<void> {
return await axios.delete(this.getApiUrl(GitlabRoute.REPOSITORY_DELETE).replace('{{id}}', String(repoId)));
try {
return await this.api.Projects.remove(repoId);
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async forkRepository(forkId: number, name: string, path: string, description: string, visibility: string, namespace: number): Promise<GitlabRepository> {
const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_FORK).replace('{{id}}', String(forkId)), {
async forkRepository(forkId: number, name: string, path: string, description: string, visibility: 'public' | 'internal' | 'private', namespace: number): Promise<ProjectSchema> {
try {
return await this.api.Projects.fork(forkId, {
name : name,
path : path,
description: description,
namespace_id: namespace,
namespaceId: namespace,
visibility : visibility
});
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
return response.data;
async editRepository(repoId: number, newAttributes: EditProjectOptions): Promise<ProjectSchema> {
try {
return await this.api.Projects.edit(repoId, newAttributes);
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async editRepository(repoId: number, newAttributes: Partial<GitlabRepository>): Promise<GitlabRepository> {
const response = await axios.put<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_EDIT).replace('{{id}}', String(repoId)), newAttributes);
async deleteRepositoryMember(repoId: number, userId: number, skipSubresources: boolean = false, unassignIssuables: boolean = false): Promise<void> {
try {
return await this.api.ProjectMembers.remove(repoId, userId, {
skipSubresourceS: skipSubresources,
unassignIssuables
});
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
return response.data;
async renameRepository(repoId: number, newName: string): Promise<ProjectSchema> {
try {
return await this.api.Projects.edit(repoId, {
name: newName
});
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<GitlabRepository> {
return await this.editRepository(repoId, { visibility: visibility.toString() });
async moveRepository(repoId: number, newRepoId: number): Promise<ProjectSchema> {
try {
return await this.api.Projects.transfer(repoId, newRepoId);
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async addRepositoryMember(repoId: number, userId: number, accessLevel: GitlabAccessLevel): Promise<GitlabMember> {
const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBER_ADD).replace('{{id}}', String(repoId)), {
user_id : userId,
access_level: accessLevel
});
changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<ProjectSchema> {
return this.editRepository(repoId, { visibility: visibility });
}
return response.data;
async addRepositoryMember(repoId: number, userId: number, accessLevel: Exclude<AccessLevel, AccessLevel.ADMIN>): Promise<MemberSchema> {
try {
return await this.api.ProjectMembers.add(repoId, accessLevel, { userId });
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<GitlabMember> {
const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_VARIABLES_ADD).replace('{{id}}', String(repoId)), {
key : key,
variable_type: 'env_var',
value : value,
async addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<ProjectVariableSchema> {
try {
return await this.api.ProjectVariables.create(repoId, key, value, {
variableType: 'env_var',
protected : isProtected,
masked : isMasked
});
return response.data;
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<GitlabMember> {
const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_BADGES_ADD).replace('{{id}}', String(repoId)), {
link_url : linkUrl,
image_url: imageUrl,
async addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<ProjectBadgeSchema> {
try {
return await this.api.ProjectBadges.add(repoId, linkUrl, imageUrl, {
name: name
});
return response.data;
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request): Promise<StatusCodes> {
async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request, res?: express.Response): Promise<boolean> {
// Get the Gitlab project and check if it have public or internal visibility
try {
const project: GitlabRepository = await this.getRepository(projectIdOrNamespace);
const project: ProjectSchema = await this.getRepository(projectIdOrNamespace);
if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) {
return StatusCodes.OK;
if ( [ 'public', 'internal' ].includes(project.visibility) ) {
req.session.sendResponse(res, StatusCodes.OK);
return true;
}
} catch ( e ) {
return StatusCodes.NOT_FOUND;
req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Template not found', DojoStatusCode.GITLAB_TEMPLATE_NOT_FOUND);
return false;
}
// Check if the user and dojo are members (with at least reporter access) of the project
......@@ -188,7 +211,7 @@ class GitlabManager {
dojo: false
};
members.forEach(member => {
if ( member.access_level >= GitlabAccessLevel.REPORTER ) {
if ( member.access_level >= AccessLevel.REPORTER ) {
if ( member.id === req.session.profile.id ) {
isUsersAtLeastReporter.user = true;
} else if ( member.id === Config.gitlab.account.id ) {
......@@ -197,90 +220,83 @@ class GitlabManager {
}
});
return isUsersAtLeastReporter.user && isUsersAtLeastReporter.dojo ? StatusCodes.OK : StatusCodes.UNAUTHORIZED;
if ( isUsersAtLeastReporter.user && isUsersAtLeastReporter.dojo ) {
req.session.sendResponse(res, StatusCodes.OK);
return true;
} else {
req.session.sendResponse(res, StatusCodes.UNAUTHORIZED, undefined, 'Template access unauthorized', DojoStatusCode.GITLAB_TEMPLATE_ACCESS_UNAUTHORIZED);
return false;
}
}
async protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: GitlabAccessLevel, allowedToPush: GitlabAccessLevel, allowedToUnprotect: GitlabAccessLevel): Promise<GitlabMember> {
const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_BRANCHES_PROTECT).replace('{{id}}', String(repoId)), {
name : branchName,
allow_force_push : allowForcePush,
merge_access_level : allowedToMerge.valueOf(),
push_access_level : allowedToPush.valueOf(),
unprotect_access_level: allowedToUnprotect.valueOf()
async protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: ProtectedBranchAccessLevel, allowedToPush: ProtectedBranchAccessLevel, allowedToUnprotect: ProtectedBranchAccessLevel): Promise<ProtectedBranchSchema> {
try {
return await this.api.ProtectedBranches.protect(repoId, branchName, {
allowForcePush : allowForcePush,
mergeAccessLevel : allowedToMerge,
pushAccessLevel : allowedToPush,
unprotectAccessLevel: allowedToUnprotect
});
return response.data;
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<GitlabTreeFile>> {
const address: string | undefined = this.getApiUrl(GitlabRoute.REPOSITORY_TREE).replace('{{id}}', String(repoId));
let params: Partial<parseLinkHeader.Link | { recursive: boolean, per_page: number }> | undefined = {
pagination: 'keyset',
async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<RepositoryTreeSchema>> {
try {
return await this.api.Repositories.allRepositoryTrees(repoId, {
recursive: recursive,
per_page : 100,
ref : branch
};
const results: Array<GitlabTreeFile> = [];
while ( params !== undefined ) {
const response = await axios.get<Array<GitlabTreeFile>>(address, {
params: params
});
results.push(...response.data);
if ( 'link' in response.headers ) {
params = parseLinkHeader(response.headers['link'])?.next ?? undefined;
} else {
params = undefined;
}
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
return results;
}
async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<GitlabFile> {
const response = await axios.get<GitlabFile>(this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), {
params: {
ref: branch
async getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<RepositoryFileExpandedSchema> {
try {
return await this.api.RepositoryFiles.show(repoId, filePath, branch);
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
});
return response.data;
}
private async createUpdateFile(create: boolean, repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
const axiosFunction = create ? axios.post : axios.put;
private async createUpdateFile(create: boolean, repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
try {
const gitFunction = create ? this.api.RepositoryFiles.create.bind(this.api) : this.api.RepositoryFiles.edit.bind(this.api);
await axiosFunction(this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), {
return await gitFunction(repoId, filePath, branch, fileBase64, commitMessage, {
encoding : 'base64',
branch : branch,
commit_message: commitMessage,
content : fileBase64,
author_name : authorName,
author_email : authorMail
authorName : authorName,
authorEmail: authorMail
});
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
async createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
return this.createUpdateFile(true, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail);
}
async updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail);
}
async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) {
await axios.delete(this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), {
data: {
branch : branch,
commit_message: commitMessage,
author_name : authorName,
author_email : authorMail
}
async deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<void> {
try {
return await this.api.RepositoryFiles.remove(repoId, filePath, branch, commitMessage, {
authorName : authorName,
authorEmail: authorMail
});
} catch ( e ) {
logger.error(JSON.stringify(e));
return Promise.reject(e);
}
}
}
......
import axios, { AxiosError, AxiosRequestHeaders } from 'axios';
import Config from '../config/Config';
import FormData from 'form-data';
import logger from '../shared/logging/WinstonLogger';
import SharedConfig from '../shared/config/SharedConfig';
import logger from '../shared/logging/WinstonLogger.js';
class HttpManager {
......@@ -12,33 +10,16 @@ class HttpManager {
}
private registerRequestInterceptor() {
axios.interceptors.request.use((config) => {
axios.interceptors.request.use(config => {
if ( config.data instanceof FormData ) {
config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders;
config.headers = { ...config.headers, ...config.data.getHeaders() } as AxiosRequestHeaders;
}
if ( config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 ) {
if ( !config.headers.DojoOverrideAuthorization ) {
config.headers['PRIVATE-TOKEN'] = Config.gitlab.account.token;
}
}
if ( config.headers.DojoOverrideAuthorization && 'DojoAuthorizationHeader' in config.headers && 'DojoAuthorizationValue' in config.headers ) {
config.headers[config.headers.DojoAuthorizationHeader] = config.headers.DojoAuthorizationValue;
delete config.headers.DojoOverrideAuthorization;
delete config.headers.DojoAuthorizationHeader;
delete config.headers.DojoAuthorizationValue;
}
return config;
});
}
private registerResponseInterceptor() {
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
axios.interceptors.response.use(response => response, error => {
if ( error instanceof AxiosError ) {
logger.error(`${ JSON.stringify(error.response?.data) }`);
} else {
......
import SharedConfig from '../shared/config/SharedConfig';
import SonarRoute from '../shared/types/Sonar/SonarRoute';
import axios, { AxiosInstance } from 'axios';
import Config from '../config/Config';
import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation';
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';
class SonarManager {
private instance: AxiosInstance = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
private getApiUrl(route: SonarRoute): string {
return `${ SharedConfig.sonar.url }${ route }`;
}
/**
* Assign a Gitlab Personal Access Token to a Sonar account (needed for any other request linked to gitlab)
* @private
*/
private async setPAT() {
const formData = new FormData();
formData.append('almSetting', 'dojo');
formData.append('pat', Config.gitlab.account.token);
await this.instance.post(this.getApiUrl(SonarRoute.SET_PAT), formData, {
headers: {
Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }`
}
});
}
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, {
headers: {
Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }`
}
})).data;
}
private async executeGetRequest<T>(url: string, data?: unknown) {
return (await this.instance.get<T>(url, {
headers: {
Authorization: `Basic ${ btoa(SharedConfig.sonar.token + ":") }`
},
params: data
})).data;
}
async createProjectFromGitlab(projectId: number) {
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)
}
async addQualityGate(projectKey: string, qualityGate: string) {
const formData = new FormData();
formData.append('projectKey', projectKey);
formData.append('gateName', qualityGate);
return await this.executePostRequest<undefined>(this.getApiUrl(SonarRoute.PROJECT_ADD_GATE), formData);
}
async addQualityProfile(projectKey: string, qualityProfile: string, language: string) {
const formData = new FormData();
formData.append('project', projectKey);
formData.append('qualityProfile', qualityProfile);
formData.append('language', language);
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);
}
// 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 ( 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);
}
}
}
return sonarProject;
}
async deleteProject(projectKey: string) {
const formData = new FormData();
formData.append('project', projectKey);
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)
}
async testQualityGate(gateName: string) {
try {
await this.executeGetRequest(this.getApiUrl(SonarRoute.TEST_GATE), { name: gateName });
return true;
} catch ( e ) {
return false;
}
}
async testQualityProfile(profileName: string, language: string) {
try {
const formData = new FormData();
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
);
return (resp.profiles.length > 0 && resp.profiles.some(p => p.name === profileName && p.language === language))
} catch ( e ) {
return false;
}
}
/**
* Return the sonar user login name associated with the gitlab username
* @param gitlabUsername Username to look up
*/
async findUserFromGitlabUser(gitlabUsername: string) {
const formData = new FormData();
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) {
if ( u.name == gitlabUsername ) {
return u.login;
}
}
return undefined;
}
async addUserToProject(username: string, projectKey: string, privileged: boolean) {
const permissions = ['user', 'codeviewer'];
if (privileged) {
permissions.push('issueadmin');
}
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)
}
}
async addGitlabUserToProject(gitlabUsername: string, projectKey: string, privileged: boolean) {
const username = await this.findUserFromGitlabUser(gitlabUsername);
if (username == undefined) {
return false;
}
await this.addUserToProject(username, projectKey, privileged);
return true;
}
}
export default new SonarManager();
\ No newline at end of file
import { Prisma, Tag } from '@prisma/client';
import db from '../helpers/DatabaseHelper';
class TagManager {
async get(name: string, include: Prisma.TagInclude | undefined = undefined): Promise<Tag | undefined> {
return await db.tag.findUnique({
where : {
name: name
},
include: include
}) as unknown as Tag ?? undefined;
}
}
export default new TagManager();
import { TagProposal } from '@prisma/client';
import db from '../helpers/DatabaseHelper';
class TagProposalManager {
async get(name: string | undefined = undefined): Promise<TagProposal | undefined> {
return await db.tagProposal.findUnique({
where: {
name: name
}
}) as unknown as TagProposal ?? undefined;
}
}
export default new TagProposalManager();
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import { Prisma } from '@prisma/client';
import db from '../helpers/DatabaseHelper';
import GitlabProfile from '../shared/types/Gitlab/GitlabProfile';
import { User } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper.js';
import { User } from '../types/DatabaseTypes.js';
import * as Gitlab from '@gitbeaker/rest';
class UserManager {
async getFiltered(filters: Prisma.UserWhereInput | undefined, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User> | undefined> {
return await db.user.findMany({
where : filters,
include: include
}) as unknown as Array<User> ?? undefined;
}
async getByMail(mail: string, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
return await db.user.findUnique({
where : {
......@@ -15,16 +21,16 @@ class UserManager {
}) as unknown as User ?? undefined;
}
async getById(id: number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
async getById(id: string | number, include: Prisma.UserInclude | undefined = undefined): Promise<User | undefined> {
return await db.user.findUnique({
where : {
id: id
id: Number(id)
},
include: include
}) as unknown as User ?? undefined;
}
async getUpdateFromGitlabProfile(gitlabProfile: GitlabProfile): Promise<User> {
async getUpdateFromGitlabProfile(gitlabProfile: Gitlab.ExpandedUserSchema): Promise<User> {
await db.user.upsert({
where : {
id: gitlabProfile.id
......@@ -46,7 +52,7 @@ class UserManager {
return (await this.getById(gitlabProfile.id))!;
}
async getFromGitlabUser(gitlabUser: GitlabUser, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<User | number | undefined> {
async getFromGitlabUser(gitlabUser: Gitlab.UserSchema, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<User | number | undefined> {
let user = await this.getById(gitlabUser.id, include) ?? gitlabUser.id;
if ( typeof user === 'number' && createIfNotExist ) {
......@@ -61,7 +67,7 @@ class UserManager {
return user;
}
async getFromGitlabUsers(gitlabUsers: Array<GitlabUser>, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User | number | undefined>> {
async getFromGitlabUsers(gitlabUsers: Array<Gitlab.UserSchema>, createIfNotExist: boolean = false, include: Prisma.UserInclude | undefined = undefined): Promise<Array<User | number | undefined>> {
return Promise.all(gitlabUsers.map(gitlabUser => this.getFromGitlabUser(gitlabUser, createIfNotExist, include)));
}
}
......
import express from 'express';
import Config from '../config/Config';
import Config from '../config/Config.js';
import semver from 'semver/preload';
import Session from '../controllers/Session';
import { HttpStatusCode } from 'axios';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
import Session from '../controllers/Session.js';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js';
import { StatusCodes } from 'http-status-codes';
class ClientVersionCheckerMiddleware {
register(): (req: express.Request, res: express.Response, next: express.NextFunction) => void {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
if ( req.headers['client'] && req.headers['client-version'] ) {
const requestClient = req.headers['client'] as string;
const requestClientVersion = req.headers['client-version'] as string;
......@@ -19,13 +19,15 @@ class ClientVersionCheckerMiddleware {
next();
return;
} else {
new Session().sendResponse(res, HttpStatusCode.MethodNotAllowed, {}, `Client version ${ requestClientVersion } is not supported. Please update your client.`, DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED);
new Session().sendResponse(res, StatusCodes.METHOD_NOT_ALLOWED, {}, `Client version ${ requestClientVersion } is not supported. Please update your client.`, DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED);
return;
}
}
}
new Session().sendResponse(res, HttpStatusCode.MethodNotAllowed, {}, `Unsupported client.`, DojoStatusCode.CLIENT_NOT_SUPPORTED);
new Session().sendResponse(res, StatusCodes.METHOD_NOT_ALLOWED, {}, `Unsupported client.`, DojoStatusCode.CLIENT_NOT_SUPPORTED);
} else {
next();
}
};
}
......
......@@ -3,6 +3,9 @@ import express from 'express';
import { StatusCodes } from 'http-status-codes';
import ExerciseManager from '../managers/ExerciseManager';
import AssignmentManager from '../managers/AssignmentManager';
import TagManager from '../managers/TagManager';
import TagProposalManager from '../managers/TagProposalManager';
import UserManager from '../managers/UserManager';
type GetFunction = (id: string | number, ...args: Array<unknown>) => Promise<unknown>
......@@ -27,23 +30,50 @@ class ParamsCallbackManager {
initBoundParams(req: express.Request) {
if ( !req.boundParams ) {
req.boundParams = {
user : undefined,
assignment : undefined,
exercise : undefined
exercise : undefined,
tag : undefined,
tagProposal: undefined
};
}
}
registerOnBackend(backend: Express) {
this.listenParam('userId', backend, (UserManager.getById as GetFunction).bind(UserManager), [ {
assignments: true,
exercises : {
include: {
members : true,
assignment: {
include: {
staff: true
}
}
}
}
} ], 'user');
this.listenParam('assignmentNameOrUrl', backend, (AssignmentManager.get as GetFunction).bind(AssignmentManager), [ {
exercises: true,
staff : true
} ], 'assignment');
this.listenParam('exerciseIdOrUrl', backend, (ExerciseManager.get as GetFunction).bind(ExerciseManager), [ {
assignment: true,
assignment: {
include: {
staff: true
}
},
members : true,
results : true
} ], 'exercise');
this.listenParam('tagName', backend, (TagManager.get as GetFunction).bind(TagManager), [ {
assignments: true
} ], 'tag');
this.listenParam('tagProposalName', backend, (TagProposalManager.get as GetFunction).bind(TagProposalManager), [ {} ], 'tagProposal');
}
}
......
......@@ -5,20 +5,20 @@ import { StatusCodes } from 'http-status-codes';
class ParamsValidatorMiddleware {
validate(validations: Array<ExpressValidator.ValidationChain> | ExpressValidator.Schema): (req: express.Request, res: express.Response, next: express.NextFunction) => void {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
if ( !(validations instanceof Array) ) {
validations = ExpressValidator.checkSchema(validations);
}
await Promise.all(validations.map(validation => validation.run(req)));
Promise.all(validations.map(validation => validation.run(req))).then(() => {
const errors = ExpressValidator.validationResult(req);
if ( !errors.isEmpty() ) {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { errors: errors.array() });
req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { errors: errors.array() });
return;
}
return next();
next();
});
};
}
}
......
import express from 'express';
import { StatusCodes } from 'http-status-codes';
import SecurityCheckType from '../types/SecurityCheckType';
import logger from '../shared/logging/WinstonLogger';
import AssignmentManager from '../managers/AssignmentManager';
import SecurityCheckType from '../types/SecurityCheckType.js';
import logger from '../shared/logging/WinstonLogger.js';
import AssignmentManager from '../managers/AssignmentManager.js';
import ExerciseManager from '../managers/ExerciseManager';
class SecurityMiddleware {
// 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: express.Request, res: express.Response, next: express.NextFunction) => void {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if ( checkIfConnected ) {
if ( req.session.profile === null || req.session.profile === undefined ) {
return req.session.sendResponse(res, StatusCodes.UNAUTHORIZED);
private checkIfConnected(checkIfConnected: boolean, req: express.Request): boolean {
return !checkIfConnected || (req.session.profile !== null && req.session.profile !== undefined);
}
}
let isAllowed = checkTypes.length === 0;
if ( !isAllowed ) {
for ( const checkType of checkTypes ) {
private async checkType(checkType: SecurityCheckType, req: express.Request): Promise<boolean> {
try {
switch ( String(checkType) ) {
case SecurityCheckType.TEACHING_STAFF:
isAllowed = isAllowed || req.session.profile.isTeachingStaff;
break;
case SecurityCheckType.ASSIGNMENT_STAFF:
isAllowed = isAllowed || await AssignmentManager.isUserAllowedToAccessAssignment(req.boundParams.assignment!, req.session.profile);
break;
case SecurityCheckType.ASSIGNMENT_IS_PUBLISHED:
isAllowed = isAllowed || (req.boundParams.assignment?.published ?? false);
break;
case SecurityCheckType.EXERCISE_SECRET:
isAllowed = isAllowed || (req.headers.exercisesecret as string | undefined) === req.boundParams.exercise!.secret;
break;
case SecurityCheckType.USER.valueOf():
return this.checkIfConnected(true, req);
case SecurityCheckType.ADMIN.valueOf():
return req.session.profile.isAdmin;
case SecurityCheckType.TEACHING_STAFF.valueOf():
return req.session.profile.isTeachingStaff;
case SecurityCheckType.EXERCISE_MEMBERS.valueOf():
return await ExerciseManager.isUserAllowedToAccessExercise(req.boundParams.exercise!, req.session.profile);
case SecurityCheckType.ASSIGNMENT_STAFF.valueOf():
return await AssignmentManager.isUserAllowedToAccessAssignment(req.boundParams.assignment!, req.session.profile);
case SecurityCheckType.ASSIGNMENT_IS_PUBLISHED.valueOf():
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:
return (req.headers.assignmentsecret as string | undefined) === req.boundParams.assignment!.secret;
default:
break;
return false;
}
} catch ( e ) {
logger.error('Security check failed !!! => ' + e);
isAllowed = isAllowed || false;
logger.error('Security check failed !!! => ' + JSON.stringify(e));
return false;
}
}
// 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: express.Request, res: express.Response, next: express.NextFunction) => void {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
if ( !this.checkIfConnected(checkIfConnected, req) ) {
return req.session.sendResponse(res, StatusCodes.UNAUTHORIZED);
}
const isAllowed: boolean = checkTypes.length === 0 ? true : checkTypes.find(async checkType => this.checkType(checkType, req)) !== undefined;
if ( !isAllowed ) {
return req.session.sendResponse(res, StatusCodes.FORBIDDEN);
}
......
import express from 'express';
import Session from '../controllers/Session';
import Session from '../controllers/Session.js';
import { Express } from 'express-serve-static-core';
class SessionMiddleware {
registerOnBackend(backend: Express) {
backend.use(async (req: express.Request, res: express.Response, next: express.NextFunction) => {
backend.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
req.session = new Session();
await req.session.initSession(req, res);
return next();
req.session.initSession(req, res).then(() => {
next();
});
});
}
}
......
import cluster, { Worker } from 'node:cluster';
import WorkerRole from './WorkerRole';
import WorkerRole from './WorkerRole.js';
import os from 'os';
import ClusterStrategy from './ClusterStrategy';
import WorkerPool from './WorkerPool';
import logger from '../shared/logging/WinstonLogger';
import ClusterStrategy from './ClusterStrategy.js';
import WorkerPool from './WorkerPool.js';
import logger from '../shared/logging/WinstonLogger.js';
/*
......@@ -11,10 +11,13 @@ import logger from '../shared/logging/WinstonLogger';
*/
class ClusterManager {
public static readonly CORES = os.cpus().length;
private readonly strategy: ClusterStrategy;
private workers: { [pid: number]: WorkerRole; } = [];
constructor(private strategy: ClusterStrategy) {}
constructor(strategy: ClusterStrategy) {
this.strategy = strategy;
}
private getWorkerPool(role: WorkerRole): WorkerPool | undefined {
return this.strategy.find(elem => elem.role === role);
......
import WorkerPool from './WorkerPool';
import WorkerPool from './WorkerPool.js';
type ClusterStrategy = Array<WorkerPool>
......
import WorkerRole from './WorkerRole';
import WorkerTask from './WorkerTask';
import WorkerRole from './WorkerRole.js';
import WorkerTask from './WorkerTask.js';
/*
......
import { Express } from 'express-serve-static-core';
import RoutesManager from '../express/RoutesManager';
import BaseRoutes from './BaseRoutes';
import SessionRoutes from './SessionRoutes';
import AssignmentRoutes from './AssignmentRoutes';
import GitlabRoutes from './GitlabRoutes';
import ExerciseRoutes from './ExerciseRoutes';
import RoutesManager from '../express/RoutesManager.js';
import BaseRoutes from './BaseRoutes.js';
import SessionRoutes from './SessionRoutes.js';
import AssignmentRoutes from './AssignmentRoutes.js';
import GitlabRoutes from './GitlabRoutes.js';
import ExerciseRoutes from './ExerciseRoutes.js';
import TagsRoutes from './TagRoutes';
import UserRoutes from './UserRoutes';
import SonarRoutes from './SonarRoutes';
class AdminRoutesManager implements RoutesManager {
......@@ -14,6 +17,9 @@ class AdminRoutesManager implements RoutesManager {
GitlabRoutes.registerOnBackend(backend);
AssignmentRoutes.registerOnBackend(backend);
ExerciseRoutes.registerOnBackend(backend);
TagsRoutes.registerOnBackend(backend);
UserRoutes.registerOnBackend(backend);
SonarRoutes.registerOnBackend(backend);
}
}
......
import { Express } from 'express-serve-static-core';
import express from 'express';
import express, { RequestHandler } from 'express';
import * as ExpressValidator from 'express-validator';
import { StatusCodes } from 'http-status-codes';
import RoutesManager from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import SecurityCheckType from '../types/SecurityCheckType';
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import GitlabManager from '../managers/GitlabManager';
import Config from '../config/Config';
import GitlabMember from '../shared/types/Gitlab/GitlabMember';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
import { AxiosError, HttpStatusCode } from 'axios';
import logger from '../shared/logging/WinstonLogger';
import DojoValidators from '../helpers/DojoValidators';
import { Prisma } from '@prisma/client';
import db from '../helpers/DatabaseHelper';
import { Assignment } from '../types/DatabaseTypes';
import AssignmentManager from '../managers/AssignmentManager';
import GitlabVisibility from '../shared/types/Gitlab/GitlabVisibility';
import RoutesManager from '../express/RoutesManager.js';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware.js';
import SecurityMiddleware from '../middlewares/SecurityMiddleware.js';
import SecurityCheckType from '../types/SecurityCheckType.js';
import GitlabManager from '../managers/GitlabManager.js';
import Config from '../config/Config.js';
import logger from '../shared/logging/WinstonLogger.js';
import DojoValidators from '../helpers/DojoValidators.js';
import { Language, Prisma } from '@prisma/client';
import db from '../helpers/DatabaseHelper.js';
import { Assignment, Exercise } from '../types/DatabaseTypes.js';
import AssignmentManager from '../managers/AssignmentManager.js';
import fs from 'fs';
import path from 'path';
import SharedAssignmentHelper from '../shared/helpers/Dojo/SharedAssignmentHelper';
import GlobalHelper from '../helpers/GlobalHelper';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
import DojoModelsHelper from '../helpers/DojoModelsHelper';
import SharedAssignmentHelper from '../shared/helpers/Dojo/SharedAssignmentHelper.js';
import GlobalHelper from '../helpers/GlobalHelper.js';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js';
import DojoModelsHelper from '../helpers/DojoModelsHelper.js';
import * as Gitlab from '@gitbeaker/rest';
import { GitbeakerRequestError } from '@gitbeaker/requester-utils';
import SharedConfig from '../shared/config/SharedConfig.js';
import SharedSonarManager from '../shared/managers/SharedSonarManager';
import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation';
import SonarManager from '../managers/SonarManager';
import { v4 as uuidv4 } from 'uuid';
class AssignmentRoutes implements RoutesManager {
......@@ -43,6 +44,21 @@ class AssignmentRoutes implements RoutesManager {
trim : true,
custom : DojoValidators.templateUrlValidator,
customSanitizer: DojoValidators.templateUrlSanitizer
},
useSonar: {
trim : true,
notEmpty : true,
isBoolean: true
},
allowSonarFailure: {
trim : true,
notEmpty : false,
isBoolean: true
},
language: {
trim : true,
notEmpty: true,
custom : DojoValidators.supportedLanguageValidator
}
};
......@@ -51,25 +67,48 @@ class AssignmentRoutes implements RoutesManager {
trim : true,
notEmpty: true,
custom : DojoValidators.exerciseIdOrUrlValidator
},
commit : {
trim : true,
notEmpty: false
},
description : {
trim : true,
notEmpty: false
}
};
private readonly assignmentUpdateCorrigeValidator: ExpressValidator.Schema = {
commit : {
trim : true,
notEmpty: false
},
description: {
trim : true,
notEmpty: false
}
};
registerOnBackend(backend: Express) {
backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment.bind(this));
backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment.bind(this));
backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(false, SecurityCheckType.ASSIGNMENT_SECRET, SecurityCheckType.USER), this.getAssignment.bind(this) as RequestHandler);
backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment.bind(this) as RequestHandler);
backend.get('/assignments/languages', this.getLanguages.bind(this) as RequestHandler);
backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true).bind(this));
backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false).bind(this));
backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true).bind(this) as RequestHandler);
backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false).bind(this) as RequestHandler);
backend.post('/assignments/:assignmentNameOrUrl/corrections', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentAddCorrigeValidator), this.linkUpdateAssignmentCorrection(false).bind(this));
backend.patch('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.linkUpdateAssignmentCorrection(true).bind(this));
backend.post('/assignments/:assignmentNameOrUrl/corrections', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentAddCorrigeValidator), this.linkUpdateAssignmentCorrection(false).bind(this) as RequestHandler);
backend.patch('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), ParamsValidatorMiddleware.validate(this.assignmentUpdateCorrigeValidator), this.linkUpdateAssignmentCorrection(true).bind(this) as RequestHandler);
backend.delete('/assignments/:assignmentNameOrUrl/corrections/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.unlinkAssignmentCorrection.bind(this) as RequestHandler);
}
// Get an assignment by its name or gitlab url
private async getAssignment(req: express.Request, res: express.Response) {
const assignment: Partial<Assignment> | undefined = req.boundParams.assignment;
if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) {
if ( assignment ) {
if ( !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment as Assignment, req.session.profile) ) {
delete assignment.gitlabId;
delete assignment.gitlabLink;
delete assignment.gitlabCreationInfo;
......@@ -79,74 +118,122 @@ class AssignmentRoutes implements RoutesManager {
delete assignment.exercises;
}
return assignment ? req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(assignment)) : res.status(StatusCodes.NOT_FOUND).send();
const getExercises = req.query.getMyExercises;
let exercises: Array<Omit<Exercise, 'assignment'>> = [];
if ( getExercises ) {
exercises = await db.exercise.findMany({
where : {
assignmentName: assignment.name,
members : {
some: {
id: req.session.profile.id
}
},
deleted : false
},
include: {
assignment: false,
members : true,
results : true,
tags : true
}
});
}
return req.session.sendResponse(res, StatusCodes.OK, DojoModelsHelper.getFullSerializableObject(Object.assign(assignment, { myExercises: exercises })));
} else {
return res.status(StatusCodes.NOT_FOUND).send();
}
}
private async createAssignment(req: express.Request, res: express.Response) {
const params: {
name: string, members: Array<GitlabUser>, template: string
name: string, members: Array<Gitlab.UserSchema>, template: string, useSonar: string, sonarGate: string, sonarProfiles: string, language: string, allowSonarFailure: string | undefined
} = req.body;
const useSonar = params.useSonar === 'true';
const allowSonarFailure = params.allowSonarFailure === 'true';
params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ];
params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id);
if ( useSonar && !(await SharedSonarManager.isSonarSupported()) ) {
return req.session.sendResponse(res, StatusCodes.UNPROCESSABLE_ENTITY, {}, `Sonar integration is not supported`, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR);
}
let repository: Gitlab.ProjectSchema;
const secret: string = uuidv4();
let repository: GitlabRepository;
try {
repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template);
} catch ( error ) {
logger.error('Repo creation error');
logger.error(error);
logger.error(JSON.stringify(error));
if ( error instanceof AxiosError ) {
if ( error.response?.data.message.name && error.response.data.message.name == 'has already been taken' ) {
return res.status(StatusCodes.CONFLICT).send();
if ( error instanceof GitbeakerRequestError ) {
if ( error.cause?.description ) {
const description = error.cause.description as unknown;
if ( GlobalHelper.isRepoNameAlreadyTaken(description) ) {
req.session.sendResponse(res, StatusCodes.CONFLICT, {}, `Repository name has already been taken`, DojoStatusCode.ASSIGNMENT_NAME_CONFLICT);
return;
}
}
return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
req.session.sendResponse(res, error.cause?.response.status ?? StatusCodes.INTERNAL_SERVER_ERROR);
return;
}
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR);
return;
}
await new Promise(resolve => setTimeout(resolve, Config.gitlab.repository.timeoutAfterCreation));
try {
await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status');
} catch ( error ) {
return GlobalHelper.repositoryCreationError('Repo params error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
}
try {
await GitlabManager.deleteFile(repository.id, '.gitlab-ci.yml', 'Remove .gitlab-ci.yml');
} catch ( error ) { /* empty */ }
const repoCreationFnExec = GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
try {
await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
} catch ( error ) {
return GlobalHelper.repositoryCreationError('CI file error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
await repoCreationFnExec(() => GitlabManager.protectBranch(repository.id, '*', true, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.ADMIN), 'Branch protection modification error');
await repoCreationFnExec(() => GitlabManager.addRepositoryVariable(repository.id, 'DOJO_ASSIGNMENT_NAME', repository.name, false, false), 'Add repo variable "DOJO_ASSIGNMENT_NAME" error');
await repoCreationFnExec(() => GitlabManager.addRepositoryVariable(repository.id, 'DOJO_ASSIGNMENT_SECRET', secret, false, true), 'Add repo variable "DOJO_ASSIGNMENT_SECRET" error');
await repoCreationFnExec(() => GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status'), 'Pipeline badge addition error');
await repoCreationFnExec(() => GitlabManager.deleteFile(repository.id, '.gitlab-ci.yml', 'Remove .gitlab-ci.yml'));
await repoCreationFnExec(() => GitlabManager.createFile(repository.id, '.gitlab-ci.yml', Buffer.from(fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'utf8').replace('{{DOCKERHUB_REPO_ASSIGNMENT_CHECKER}}', Config.dockerhub.repositories.assignmentChecker)).toString('base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'), 'CI/CD file creation error');
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;
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;
}
try {
await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => {
try {
return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
} catch ( error ) {
logger.error('Add member error');
logger.error(error);
return false;
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) ) {
throw new Error();
}
}, 'Sonar add member error');
}
}));
const assignment: Assignment = await db.assignment.create({
const assignment: Assignment = await repoCreationFnExec(() => db.assignment.create({
data: {
name : repository.name,
gitlabId : repository.id,
gitlabLink : repository.web_url,
gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
gitlabCreationDate: new Date(),
gitlabLastInfo : repository as unknown as Prisma.JsonObject,
gitlabLastInfoDate: new Date(),
useSonar : useSonar,
allowSonarFailure : allowSonarFailure,
sonarKey : sonarProject?.project.key,
sonarCreationInfo : sonarProject?.project,
sonarGate : params.sonarGate,
sonarProfiles : params.sonarProfiles,
language : Language[params.language as keyof typeof Language],
secret : secret,
staff : {
connectOrCreate: [ ...params.members.map(gitlabUser => {
return {
......@@ -161,11 +248,12 @@ class AssignmentRoutes implements RoutesManager {
}) ]
}
}
}) as unknown as Assignment;
}), 'Database error') as Assignment;
return req.session.sendResponse(res, StatusCodes.OK, assignment);
req.session.sendResponse(res, StatusCodes.OK, assignment);
} catch ( error ) {
return GlobalHelper.repositoryCreationError('DB error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_GITLAB_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_INTERNAL_ERROR, repository);
/* Empty */
}
}
......@@ -174,12 +262,13 @@ class AssignmentRoutes implements RoutesManager {
if ( publish ) {
const isPublishable = await SharedAssignmentHelper.isPublishable(req.boundParams.assignment!.gitlabId);
if ( !isPublishable.isPublishable ) {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code);
req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code);
return;
}
}
try {
await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);
await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? 'internal' : 'private');
await db.assignment.update({
where: {
......@@ -192,20 +281,21 @@ class AssignmentRoutes implements RoutesManager {
req.session.sendResponse(res, StatusCodes.OK);
} catch ( error ) {
if ( error instanceof AxiosError ) {
res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
logger.error(JSON.stringify(error));
if ( error instanceof GitbeakerRequestError ) {
req.session.sendResponse(res, error.cause?.response.status ?? StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'Error while updating the assignment state');
return;
}
logger.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'Error while updating the assignment state');
}
};
}
private linkUpdateAssignmentCorrection(isUpdate: boolean): (req: express.Request, res: express.Response) => Promise<void> {
return async (req: express.Request, res: express.Response): Promise<void> => {
if ( req.boundParams.exercise?.assignmentName != req.boundParams.assignment?.name ) {
if ( req.boundParams.exercise?.assignmentName !== req.boundParams.assignment?.name ) {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'The exercise does not belong to the assignment', DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED);
}
......@@ -219,27 +309,60 @@ class AssignmentRoutes implements RoutesManager {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST);
}
const lastCommit = await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId);
if ( lastCommit ) {
if ( !isUpdate ) {
await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise!.gitlabId, GitlabVisibility.INTERNAL);
const commit: Gitlab.CommitSchema | undefined = req.body.commit ? await GitlabManager.getRepositoryCommit(req.boundParams.exercise!.gitlabId, req.body.commit as string) : await GitlabManager.getRepositoryLastCommit(req.boundParams.exercise!.gitlabId);
if ( commit ) {
if ( !isUpdate && SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects
await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise!.gitlabId, 'internal');
}
await db.exercise.update({
where: {
id: req.boundParams.exercise!.id
},
data : {
correctionCommit: lastCommit
}
data : Object.assign({
correctionCommit: commit
}, isUpdate && req.body.description === undefined ? {} : {
correctionDescription: req.body.description
})
});
return req.session.sendResponse(res, StatusCodes.OK);
} else {
return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, undefined, 'No last commit found');
return req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Commit not found');
}
};
}
private async unlinkAssignmentCorrection(req: express.Request, res: express.Response) {
if ( req.boundParams.exercise?.assignmentName !== req.boundParams.assignment?.name ) {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'The exercise does not belong to the assignment', DojoStatusCode.ASSIGNMENT_EXERCISE_NOT_RELATED);
}
if ( !req.boundParams.exercise?.isCorrection ) {
return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, undefined, 'This exercise is not a correction', DojoStatusCode.EXERCISE_CORRECTION_NOT_EXIST);
}
if ( SharedConfig.production ) { //Disable in dev env because gitlab dev group is private and we can't change visibility of sub projects
await GitlabManager.changeRepositoryVisibility(req.boundParams.exercise.gitlabId, 'private');
}
await db.exercise.update({
where: {
id: req.boundParams.exercise.id
},
data : {
correctionCommit : Prisma.DbNull,
correctionDescription: null
}
});
return req.session.sendResponse(res, StatusCodes.OK);
}
private async getLanguages(req: express.Request, res: express.Response) {
req.session.sendResponse(res, StatusCodes.OK, Object.values(Language));
}
}
......
import { Express } from 'express-serve-static-core';
import express from 'express';
import express, { RequestHandler } from 'express';
import { StatusCodes } from 'http-status-codes';
import RoutesManager from '../express/RoutesManager';
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';
class BaseRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.get('/', this.homepage.bind(this));
backend.get('/health_check', this.healthCheck.bind(this));
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('/clients_config', this.clientsConfig.bind(this) as RequestHandler);
}
private async homepage(req: express.Request, res: express.Response) {
......@@ -17,6 +26,24 @@ class BaseRoutes implements RoutesManager {
private async healthCheck(req: express.Request, res: express.Response) {
return req.session.sendResponse(res, StatusCodes.OK);
}
private async clientsConfig(req: express.Request, res: express.Response) {
return req.session.sendResponse(res, StatusCodes.OK, {
gitlabUrl : Config.gitlab.url,
gitlabAccountId : Config.gitlab.account.id,
gitlabAccountUsername : Config.gitlab.account.username,
loginGitlabClientId : Config.login.gitlab.client.id,
exerciseMaxPerAssignment: Config.exercise.maxPerAssignment
});
}
private async sonar(req: express.Request, res: express.Response) {
const data = {
sonarEnabled: await SharedSonarManager.isSonarSupported(),
languages: await SonarManager.getLanguages()
};
return req.session.sendResponse(res, StatusCodes.OK, data);
}
}
......
import { Express } from 'express-serve-static-core';
import express from 'express';
import express, { RequestHandler } from 'express';
import * as ExpressValidator from 'express-validator';
import { StatusCodes } from 'http-status-codes';
import RoutesManager from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import GitlabUser from '../shared/types/Gitlab/GitlabUser';
import GitlabManager from '../managers/GitlabManager';
import Config from '../config/Config';
import GitlabRepository from '../shared/types/Gitlab/GitlabRepository';
import { AxiosError } from 'axios';
import logger from '../shared/logging/WinstonLogger';
import DojoValidators from '../helpers/DojoValidators';
import RoutesManager from '../express/RoutesManager.js';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware.js';
import SecurityMiddleware from '../middlewares/SecurityMiddleware.js';
import GitlabManager from '../managers/GitlabManager.js';
import Config from '../config/Config.js';
import logger from '../shared/logging/WinstonLogger.js';
import DojoValidators from '../helpers/DojoValidators.js';
import { v4 as uuidv4 } from 'uuid';
import GitlabMember from '../shared/types/Gitlab/GitlabMember';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import { Prisma } from '@prisma/client';
import { Assignment, Exercise } from '../types/DatabaseTypes';
import db from '../helpers/DatabaseHelper';
import SecurityCheckType from '../types/SecurityCheckType';
import GitlabTreeFile from '../shared/types/Gitlab/GitlabTreeFile';
import GitlabFile from '../shared/types/Gitlab/GitlabFile';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType';
import { Assignment, Exercise } from '../types/DatabaseTypes.js';
import db from '../helpers/DatabaseHelper.js';
import SecurityCheckType from '../types/SecurityCheckType.js';
import JSON5 from 'json5';
import fs from 'fs';
import path from 'path';
import AssignmentFile from '../shared/types/Dojo/AssignmentFile';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
import GlobalHelper from '../helpers/GlobalHelper';
import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats';
import ExerciseManager from '../managers/ExerciseManager';
import AssignmentFile from '../shared/types/Dojo/AssignmentFile.js';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile.js';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js';
import GlobalHelper from '../helpers/GlobalHelper.js';
import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats.js';
import ExerciseManager from '../managers/ExerciseManager.js';
import * as Gitlab from '@gitbeaker/rest';
import { ProjectSchema } from '@gitbeaker/rest';
import GitlabTreeFileType from '../shared/types/Gitlab/GitlabTreeFileType.js';
import { GitbeakerRequestError } from '@gitbeaker/requester-utils';
import SonarProjectCreation from '../shared/types/Sonar/SonarProjectCreation';
import SonarManager from '../managers/SonarManager';
class ExerciseRoutes implements RoutesManager {
......@@ -66,108 +64,247 @@ class ExerciseRoutes implements RoutesManager {
archiveBase64: {
isBase64: true,
notEmpty: true
},
sonarGatePass: {
trim : true,
notEmpty : false,
isBoolean: true
}
};
registerOnBackend(backend: Express) {
backend.post('/assignments/:assignmentNameOrUrl/exercises', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciseValidator), this.createExercise.bind(this));
backend.post('/assignments/:assignmentNameOrUrl/exercises', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_IS_PUBLISHED), ParamsValidatorMiddleware.validate(this.exerciseValidator), this.createExercise.bind(this) as RequestHandler);
backend.get('/exercises', SecurityMiddleware.check(true, SecurityCheckType.ADMIN), this.getAllExercises.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrUrl/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrUrl', SecurityMiddleware.check(false, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS, SecurityCheckType.EXERCISE_SECRET), this.getExercise.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrUrl/members', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.getExerciseMembers.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.getExerciseResults.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrUrl/assignment', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), this.getAssignment.bind(this));
backend.delete('/exercises/:exerciseIdOrUrl', SecurityMiddleware.check(true, SecurityCheckType.ADMIN, SecurityCheckType.EXERCISE_MEMBERS), this.deleteExercise.bind(this) as RequestHandler);
backend.post('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this));
backend.get('/users/:userId/exercises', SecurityMiddleware.check(true), this.getUserExercises.bind(this) as RequestHandler);
backend.get('/exercises/:exerciseIdOrLink/results', SecurityMiddleware.check(true), this.getExerciseResultsByIdOrLink.bind(this) as RequestHandler);
backend.post('/exercises/:exerciseIdOrUrl/results', SecurityMiddleware.check(false, SecurityCheckType.EXERCISE_SECRET), ParamsValidatorMiddleware.validate(this.resultValidator), this.createResult.bind(this) as RequestHandler);
}
private getExerciseName(assignment: Assignment, members: Array<GitlabUser>, suffix: number): string {
return `DojoEx - ${ assignment.name } - ${ members.map(member => member.username).sort((a, b) => a.localeCompare(b)).join(' + ') }${ suffix > 0 ? ` - ${ suffix }` : '' }`;
private async getExerciseResultsByIdOrLink(req: express.Request, res: express.Response) {
const exerciseIdOrLink = req.params.exerciseIdOrLink;
const exercise = await db.exercise.findFirst({
where: {
OR: [ { id: exerciseIdOrLink }, { gitlabLink: exerciseIdOrLink } ]
}
});
private getExercisePath(assignment: Assignment, exerciseId: string): string {
return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as GitlabRepository).path }_${ exerciseId }`;
if ( !exercise ) {
return res.status(StatusCodes.NOT_FOUND).send('Exercise not found');
}
private async createExercise(req: express.Request, res: express.Response) {
const params: { members: Array<GitlabUser> } = req.body;
params.members = [ await req.session.profile.gitlabProfile!.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id);
const assignment: Assignment = req.boundParams.assignment!;
const results = await db.result.findMany({
where: { exerciseId: exercise.id }
});
return res.status(StatusCodes.OK).json(results);
}
private async getAllExercises(req: express.Request, res: express.Response) {
const exos = await db.exercise.findMany();
return req.session.sendResponse(res, StatusCodes.OK, exos);
}
private async getUserExercises(req: express.Request, res: express.Response) {
if ( req.boundParams.user ) {
if ( req.session.profile.isAdmin || req.session.profile.id === req.boundParams.user.id ) {
return req.session.sendResponse(res, StatusCodes.OK, req.boundParams.user.exercises.filter(exercise => !exercise.deleted));
} else {
return req.session.sendResponse(res, StatusCodes.FORBIDDEN);
}
} else {
return req.session.sendResponse(res, StatusCodes.NOT_FOUND);
}
}
private getExerciseName(assignment: Assignment, members: Array<Gitlab.UserSchema>, suffix: number): string {
const memberNames: string = members.map(member => member.username).sort((a, b) => a.localeCompare(b)).join(' + ');
const suffixString: string = suffix > 0 ? ` - ${ suffix }` : '';
return `DojoEx - ${ assignment.name } - ${ memberNames }${ suffixString }`;
}
private async getExercise(req: express.Request, res: express.Response) {
return req.session.sendResponse(res, StatusCodes.OK, req.boundParams.exercise!);
}
private async getExerciseMembers(req: express.Request, res: express.Response) {
const repoId = req.boundParams.exercise!.gitlabId;
const members = await GitlabManager.getRepositoryMembers(String(repoId));
return req.session.sendResponse(res, StatusCodes.OK, members);
}
private async getExerciseResults(req: express.Request, res: express.Response) {
const results = await db.result.findMany({
where : { exerciseId: req.boundParams.exercise!.id },
orderBy: { dateTime: 'desc' }
});
return req.session.sendResponse(res, StatusCodes.OK, results);
}
private async deleteExercise(req: express.Request, res: express.Response) {
const repoId = req.boundParams.exercise!.gitlabId;
const members = await GitlabManager.getRepositoryMembers(String(repoId), false);
for ( const member of members ) {
if ( member.id !== Config.gitlab.account.id ) {
await GitlabManager.deleteRepositoryMember(repoId, member.id);
}
}
// We rename (with unique str added) the repository before moving it because of potential name conflicts
const newName: string = `${ req.boundParams.exercise!.name }_${ uuidv4() }`;
await GitlabManager.renameRepository(repoId, newName);
const repository: ProjectSchema = await GitlabManager.moveRepository(repoId, Config.gitlab.group.deletedExercises);
await db.exercise.update({
where: { id: req.boundParams.exercise!.id },
data : {
name : newName,
gitlabLastInfo : repository as unknown as Prisma.JsonObject,
gitlabLastInfoDate: new Date(),
deleted : true
}
});
const exercises: Array<Exercise> | undefined = await ExerciseManager.getFromAssignment(assignment.name, { members: true });
const reachedLimitUsers: Array<GitlabUser> = [];
if ( exercises ) {
for ( const member of params.members ) {
return req.session.sendResponse(res, StatusCodes.OK);
}
private getExercisePath(assignment: Assignment, exerciseId: string): string {
return `dojo-ex_${ (assignment.gitlabLastInfo as unknown as Gitlab.ProjectSchema).path }_${ exerciseId }`;
}
private async checkExerciseLimit(assignment: Assignment, members: Array<Gitlab.UserSchema>): Promise<Array<Gitlab.UserSchema>> {
const exercises: Array<Exercise> | undefined = await ExerciseManager.getFromAssignment(assignment.name, false, { members: true });
const reachedLimitUsers: Array<Gitlab.UserSchema> = [];
if ( exercises.length > 0 ) {
for ( const member of members ) {
const exerciseCount: number = exercises.filter(exercise => exercise.members.findIndex(exerciseMember => exerciseMember.id === member.id) !== -1).length;
if ( exerciseCount >= Config.exercise.maxPerAssignment ) {
reachedLimitUsers.push(member);
}
}
}
if ( reachedLimitUsers.length > 0 ) {
return req.session.sendResponse(res, StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE, reachedLimitUsers, 'Max exercise per assignment reached', DojoStatusCode.MAX_EXERCISE_PER_ASSIGNMENT_REACHED);
}
return reachedLimitUsers;
}
const exerciseId: string = uuidv4();
const secret: string = uuidv4();
let repository!: GitlabRepository;
private async createExerciseRepository(assignment: Assignment, members: Array<Gitlab.UserSchema>, exerciseId: string, req: express.Request, res: express.Response): Promise<Gitlab.ProjectSchema | undefined> {
let repository!: Gitlab.ProjectSchema;
let suffix: number = 0;
do {
try {
repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as GitlabRepository).id, this.getExerciseName(assignment, params.members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises);
repository = await GitlabManager.forkRepository((assignment.gitlabCreationInfo as unknown as Gitlab.ProjectSchema).id, this.getExerciseName(assignment, members, suffix), this.getExercisePath(req.boundParams.assignment!, exerciseId), Config.exercise.default.description.replace('{{ASSIGNMENT_NAME}}', assignment.name), Config.exercise.default.visibility, Config.gitlab.group.exercises);
break;
} catch ( error ) {
logger.error('Repo creation error');
logger.error(error);
logger.error(JSON.stringify(error));
if ( error instanceof AxiosError ) {
if ( error.response?.data.message.name && error.response.data.message.name == 'has already been taken' ) {
if ( error instanceof GitbeakerRequestError && error.cause?.description ) {
const description = error.cause.description as unknown;
if ( GlobalHelper.isRepoNameAlreadyTaken(description) ) {
suffix++;
} else {
return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, 'Unknown gitlab error while forking repository', DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR);
req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, 'Unknown gitlab error while forking repository', DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR);
return undefined;
}
} else {
return req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, 'Unknown error while forking repository', DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR);
req.session.sendResponse(res, StatusCodes.INTERNAL_SERVER_ERROR, {}, 'Unknown error while forking repository', DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR);
return undefined;
}
}
} while ( suffix < Config.exercise.maxSameName );
if ( suffix >= Config.exercise.maxSameName ) {
logger.error('Max exercise with same name reached');
return res.status(StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE).send();
return repository;
}
private async createExercise(req: express.Request, res: express.Response) {
const params: { members: Array<Gitlab.UserSchema> } = req.body;
params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ].removeObjectDuplicates(gitlabUser => gitlabUser.id);
const assignment: Assignment = req.boundParams.assignment!;
const reachedLimitUsers: Array<Gitlab.UserSchema> = await this.checkExerciseLimit(assignment, params.members);
if ( reachedLimitUsers.length > 0 ) {
req.session.sendResponse(res, StatusCodes.INSUFFICIENT_SPACE_ON_RESOURCE, reachedLimitUsers, 'Max exercise per assignment reached', DojoStatusCode.MAX_EXERCISE_PER_ASSIGNMENT_REACHED);
return;
}
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;
if ( !repository ) {
return;
}
await new Promise(resolve => setTimeout(resolve, Config.gitlab.repository.timeoutAfterCreation));
const repoCreationFnExec = GlobalHelper.repoCreationFnExecCreator(req, res, DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository);
try {
await GitlabManager.protectBranch(repository.id, '*', false, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);
await repoCreationFnExec(() => GitlabManager.protectBranch(repository.id, '*', false, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.DEVELOPER, Gitlab.AccessLevel.ADMIN), 'Branch protection modification error');
await repoCreationFnExec(() => GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status'), 'Pipeline badge addition error');
await repoCreationFnExec(async () => {
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_EXERCISE_ID', exerciseId, false, true);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_SECRET', secret, false, true);
await GitlabManager.addRepositoryVariable(repository.id, 'DOJO_RESULTS_FOLDER', Config.exercise.pipelineResultsFolder, false, false);
}, 'Pipeline variables addition error');
await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status');
} catch ( error ) {
return GlobalHelper.repositoryCreationError('Repo params error', error, req, res, DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository);
}
await repoCreationFnExec(() => GitlabManager.updateFile(repository.id, '.gitlab-ci.yml', Buffer.from(fs.readFileSync(path.join(__dirname, '../../assets/exercise_gitlab_ci.yml'), 'utf8').replace('{{DOCKERHUB_REPO_EXERCISE_CHECKER}}', Config.dockerhub.repositories.exerciseChecker)).toString('base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)'), 'CI/CD file update error');
try {
await GitlabManager.updateFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/exercise_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
} catch ( error ) {
return GlobalHelper.repositoryCreationError('CI file update error', error, req, res, DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository);
await repoCreationFnExec(async () => Promise.all([ ...new Set([ ...assignment.staff, ...params.members ].map(member => member.id)) ].map(GlobalHelper.addRepoMember(repository.id))), 'Add repository members error');
// Create Sonar project
if ( assignment.useSonar && assignment.sonarProfiles != null ) {
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 {
await Promise.all([ ...new Set([ ...assignment.staff.map(user => user.id), ...params.members.map(member => member.id) ]) ].map(async (memberId: number): Promise<GitlabMember | false> => {
try {
return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
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);
}
}
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('Add member error');
logger.error('Sonar add member error');
logger.error(error);
return false;
return GlobalHelper.repositoryCreationError('Sonar add member error', error, req, res, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, DojoStatusCode.ASSIGNMENT_CREATION_SONAR_ERROR, repository, sonarProject);
}
}));
}
const exercise: Exercise = await db.exercise.create({
let exercise: Exercise = await repoCreationFnExec(() => db.exercise.create({
data: {
id : exerciseId,
assignmentName : assignment.name,
......@@ -176,8 +313,11 @@ class ExerciseRoutes implements RoutesManager {
gitlabId : repository.id,
gitlabLink : repository.web_url,
gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
gitlabCreationDate: new Date(),
gitlabLastInfo : repository as unknown as Prisma.JsonObject,
gitlabLastInfoDate: new Date(),
sonarKey : sonarProject?.project.key,
sonarCreationInfo : sonarProject?.project,
members : {
connectOrCreate: [ ...params.members.map(gitlabUser => {
return {
......@@ -192,19 +332,29 @@ class ExerciseRoutes implements RoutesManager {
}) ]
}
}
}) as unknown as Exercise;
})) as Exercise;
return req.session.sendResponse(res, StatusCodes.OK, exercise);
exercise = await ExerciseManager.get(exercise.id, {
members : true,
assignment: {
include: {
staff: true
}
}
}) as Exercise;
req.session.sendResponse(res, StatusCodes.OK, exercise);
return;
} catch ( error ) {
return GlobalHelper.repositoryCreationError('DB error', error, req, res, DojoStatusCode.EXERCISE_CREATION_GITLAB_ERROR, DojoStatusCode.EXERCISE_CREATION_INTERNAL_ERROR, repository);
/* Empty */
}
}
private async getAssignment(req: express.Request, res: express.Response) {
const repoTree: Array<GitlabTreeFile> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
const repoTree: Array<Gitlab.RepositoryTreeSchema> = await GitlabManager.getRepositoryTree(req.boundParams.exercise!.assignment.gitlabId);
let assignmentHjsonFile!: GitlabFile;
const immutableFiles: Array<GitlabFile> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
let assignmentHjsonFile!: Gitlab.RepositoryFileExpandedSchema;
const immutableFiles: Array<Gitlab.RepositoryFileExpandedSchema> = await Promise.all(Config.assignment.baseFiles.map(async (baseFile: string) => {
const file = await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, baseFile);
if ( baseFile === Config.assignment.filename ) {
......@@ -214,12 +364,12 @@ class ExerciseRoutes implements RoutesManager {
return file;
}));
const dojoAssignmentFile: AssignmentFile = JSON5.parse(atob(assignmentHjsonFile.content)) as AssignmentFile;
const dojoAssignmentFile: AssignmentFile = JSON5.parse(atob(assignmentHjsonFile.content));
const immutablePaths = dojoAssignmentFile.immutable.map(fileDescriptor => fileDescriptor.path);
await Promise.all(repoTree.map(async gitlabTreeFile => {
if ( gitlabTreeFile.type == GitlabTreeFileType.BLOB ) {
if ( gitlabTreeFile.type === GitlabTreeFileType.BLOB.valueOf() ) {
for ( const immutablePath of immutablePaths ) {
if ( gitlabTreeFile.path.startsWith(immutablePath) ) {
immutableFiles.push(await GitlabManager.getFile(req.boundParams.exercise!.assignment.gitlabId, gitlabTreeFile.path));
......@@ -237,14 +387,16 @@ 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 } = req.body;
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 result = await db.result.create({
data: {
exerciseId : exercise.id,
exitCode : params.exitCode,
success : params.results.success!,
sonarGatePass: sonarGate,
commit : params.commit,
results : params.results as unknown as Prisma.JsonObject,
files : params.files
......@@ -259,3 +411,5 @@ class ExerciseRoutes implements RoutesManager {
export default new ExerciseRoutes();
import { Express } from 'express-serve-static-core';
import express from 'express';
import RoutesManager from '../express/RoutesManager';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import SecurityCheckType from '../types/SecurityCheckType';
import GitlabManager from '../managers/GitlabManager';
import express, { RequestHandler } from 'express';
import RoutesManager from '../express/RoutesManager.js';
import SecurityMiddleware from '../middlewares/SecurityMiddleware.js';
import SecurityCheckType from '../types/SecurityCheckType.js';
import GitlabManager from '../managers/GitlabManager.js';
class GitlabRoutes implements RoutesManager {
registerOnBackend(backend: Express) {
backend.get('/gitlab/project/:gitlabProjectIdOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess.bind(this));
backend.get('/gitlab/project/:gitlabProjectIdOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess.bind(this) as RequestHandler);
}
private async checkTemplateAccess(req: express.Request, res: express.Response) {
const gitlabProjectIdOrNamespace: string = req.params.gitlabProjectIdOrNamespace;
return res.status(await GitlabManager.checkTemplateAccess(gitlabProjectIdOrNamespace, req)).send();
await GitlabManager.checkTemplateAccess(gitlabProjectIdOrNamespace, req, res);
}
}
......
import { Express } from 'express-serve-static-core';
import express from 'express';
import express, { RequestHandler } from 'express';
import * as ExpressValidator from 'express-validator';
import { StatusCodes } from 'http-status-codes';
import RoutesManager from '../express/RoutesManager';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware';
import SecurityMiddleware from '../middlewares/SecurityMiddleware';
import GitlabManager from '../managers/GitlabManager';
import UserManager from '../managers/UserManager';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode';
import SharedGitlabManager from '../shared/managers/SharedGitlabManager';
import Config from '../config/Config';
import RoutesManager from '../express/RoutesManager.js';
import ParamsValidatorMiddleware from '../middlewares/ParamsValidatorMiddleware.js';
import SecurityMiddleware from '../middlewares/SecurityMiddleware.js';
import GitlabManager from '../managers/GitlabManager.js';
import UserManager from '../managers/UserManager.js';
import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js';
import Config from '../config/Config.js';
class SessionRoutes implements RoutesManager {
......@@ -32,9 +31,9 @@ class SessionRoutes implements RoutesManager {
};
registerOnBackend(backend: Express) {
backend.post('/login', ParamsValidatorMiddleware.validate(this.loginValidator), this.login.bind(this));
backend.post('/refresh_tokens', ParamsValidatorMiddleware.validate(this.refreshTokensValidator), this.refreshTokens.bind(this));
backend.get('/test_session', SecurityMiddleware.check(true), this.testSession.bind(this));
backend.post('/login', ParamsValidatorMiddleware.validate(this.loginValidator), this.login.bind(this) as RequestHandler);
backend.post('/refresh_tokens', ParamsValidatorMiddleware.validate(this.refreshTokensValidator), this.refreshTokens.bind(this) as RequestHandler);
backend.get('/test_session', SecurityMiddleware.check(true), this.testSession.bind(this) as RequestHandler);
}
private async login(req: express.Request, res: express.Response) {
......@@ -64,7 +63,7 @@ class SessionRoutes implements RoutesManager {
refreshToken: string
} = req.body;
const gitlabTokens = await SharedGitlabManager.getTokens(params.refreshToken, true, Config.login.gitlab.client.secret);
const gitlabTokens = await GitlabManager.getTokens(params.refreshToken, true, Config.login.gitlab.client.secret);
req.session.sendResponse(res, StatusCodes.OK, gitlabTokens);
} catch ( error ) {
......