diff --git a/ExpressAPI/.idea/swagger-settings.xml b/ExpressAPI/.idea/swagger-settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..01d844c898848ab4b93eecee293edb955b148cf5 --- /dev/null +++ b/ExpressAPI/.idea/swagger-settings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="SwaggerSettings"> + <option name="defaultPreviewType" value="SWAGGER_UI" /> + </component> +</project> \ No newline at end of file diff --git a/ExpressAPI/assets/OpenAPI/OpenAPI.yaml b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fee35461e1d1d720ce2bd30c6c5d30f00ab206c3 --- /dev/null +++ b/ExpressAPI/assets/OpenAPI/OpenAPI.yaml @@ -0,0 +1,886 @@ +openapi: 3.1.0 +info: + title: Dojo API + version: 3.1.0 + description: | + **Backend API of the Dojo project.** + + + See more information about the projet on + [Gitlab](https://githepia.hesge.ch/dojo_project/dojo). + license: + name: AGPLv3 + identifier: AGPL-3.0-only + contact: + name: MichaΓ«l Minelli + email: dojo@minelli.me +servers: + - url: http://localhost:30993/ + description: Development + - url: http://dojo-test.edu.hesge.ch/dojo/api/ + description: Test (only from HES-GE network) + - url: https://rdps.hesge.ch/dojo/api/ + description: Production +tags: + - name: General + description: '' + - name: Session + description: Routes that are used to manage the user's session + - name: Gitlab + description: Routes that are used to provide Gitlab informations + - name: Assignment + description: Routes that are used to manage assignments + - name: Exercise + description: Routes that are used to manage exercises +paths: + /health_check: + get: + tags: + - General + summary: Health check + description: This route can be used to check if the server is up and running. + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DojoBackendResponse' + description: OK + default: + $ref: '#/components/responses/ERROR' + /login: + post: + tags: + - Session + summary: Login to Dojo app + description: | + This route can be used to connect the user to the backend and retrieve + informations about his access rights. + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + accessToken: + type: string + format: Gitlab access token + refreshToken: + type: string + format: Gitlab refresh token + required: + - accessToken + - refreshToken + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + nullable: true + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /refresh_tokens: + post: + tags: + - Session + summary: Refresh tokens + description: | + This route can be used to refresh the session. Gitlab tokens will be + refreshed and a new Dojo backend JWT token will be provided. + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + refreshToken: + type: string + format: Gitlab refresh token + required: + - refreshToken + responses: + '200': + description: The new Gitlab tokens as returned by Gitlab API. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + properties: + access_token: + type: string + examples: + - de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54 + token_type: + type: string + examples: + - bearer + expires_in: + type: number + examples: + - 7200 + refresh_token: + type: string + examples: + - 8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1 + scope: + type: array + examples: + - - api + - create_runner + - read_repository + - write_repository + items: + type: string + created_at: + type: number + examples: + - 1607635748 + required: + - access_token + - token_type + - expires_in + - refresh_token + - scope + - created_at + default: + $ref: '#/components/responses/ERROR' + /test_session: + get: + tags: + - Session + summary: Test of the session + description: This route can be used to test the validity of the session token. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DojoBackendResponse' + default: + $ref: '#/components/responses/ERROR' + /gitlab/project/{gitlabProjectIdOrNamespace}/checkTemplateAccess: + get: + tags: + - Gitlab + summary: Check access to template repository + description: | + This route can be used to check if the template repository is accessible by the Dojo user. + + **π Security needs:** any access rights + security: + - Clients_Token: [ ] + parameters: + - $ref: '#/components/parameters/gitlabProjectIdOrNamespace' + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + nullable: true + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /assignments/{assignmentNameOrUrl}: + get: + tags: + - Assignment + summary: Get an assignment + description: | + This route can be used to get an assignment's informations by its name or its url. + If it's not published and the user is not a member of the staff (or admin), some informations will not be provided. + Informations that will be deleted: + - gitlabId + - gitlabLink + - gitlabCreationInfo + - gitlabLastInfo + - gitlabLastInfoDate + - staff + - exercises + + **π Security needs:** any access rights + security: + - Clients_Token: [ ] + parameters: + - $ref: '#/components/parameters/assignmentNameOrUrl' + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/Assignment' + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /assignments: + post: + tags: + - Assignment + summary: Create a new assignment + description: | + **π Security needs:** TeachingStaff or Admin roles + security: + - Clients_Token: [ ] + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + name: + type: string + description: Name of the assignment (must be unique) + members: + type: string + format: json + description: JSON string of an array of gitlab user objects + externalDocs: + description: Gitlab user object + url: https://docs.gitlab.com/ee/api/users.html#list-users + template: + type: string + format: url + description: URL of the template to use as base of the assignment + required: + - name + - members + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/Assignment' + '401': + $ref: '#/components/responses/UNAUTHORIZED' + default: + $ref: '#/components/responses/ERROR' + /assignments/{assignmentNameOrUrl}/publish: + patch: + tags: + - Assignment + summary: Publish an assignment + description: | + **π Security needs:** TeachingStaff of the assignment or Admin role + security: + - Clients_Token: [ ] + parameters: + - $ref: '#/components/parameters/assignmentNameOrUrl' + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + nullable: true + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /assignments/{assignmentNameOrUrl}/unpublish: + patch: + tags: + - Assignment + summary: Unpublish an assignment + description: | + **π Security needs:** TeachingStaff of the assignment or Admin role + security: + - Clients_Token: [ ] + parameters: + - $ref: '#/components/parameters/assignmentNameOrUrl' + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + nullable: true + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /assignments/{assignmentNameOrUrl}/exercises: + post: + tags: + - Exercise + summary: Create a new exercise + description: | + This route can be used to create a new exercise based on an assignment. + + **π Security needs:** The assignment must be published + security: + - Clients_Token: [ ] + parameters: + - $ref: '#/components/parameters/assignmentNameOrUrl' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + members: + type: string + format: json + description: JSON string of an array of gitlab user objects + externalDocs: + description: Gitlab user object + url: https://docs.gitlab.com/ee/api/users.html#list-users + required: + - members + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/Exercise' + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + '419': + description: | + At least one of the members of the exercices have reached the maximum number of exercises for this assignment. + + Users that have reached the maximum number of exercises are listed in the `data` field. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + default: + $ref: '#/components/responses/ERROR' + /exercises/{exerciseId}/assignment: + get: + tags: + - Exercise + - Assignment + summary: Get the assignment of an exercise + security: + - ExerciseChecker_Secret: [ ] + parameters: + - $ref: '#/components/parameters/exerciseId' + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + properties: + assignment: + $ref: '#/components/schemas/Assignment' + assignmentFile: + $ref: '#/components/schemas/AssignmentFile' + immutable: + type: array + items: + type: object + description: Gitlab file object + externalDocs: + description: Gitlab file object + url: https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' + /exercises/{exerciseId}/results: + post: + tags: + - Exercise + summary: Get results of an exercise + security: + - ExerciseChecker_Secret: [ ] + parameters: + - $ref: '#/components/parameters/exerciseId' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + exitCode: + type: number + description: The exit code of the exercise execution + commit: + type: string + format: json + description: JSON string of an array of gitlab user objects + externalDocs: + description: Gitlab commit object + url: https://docs.gitlab.com/ee/api/commits.html#get-a-single-commit + results: + type: string + format: json + description: JSON string of a DojoResult object (see in schemas) + externalDocs: + description: DojoResult object + url: '#/components/schemas/DojoResult' + files: + type: string + format: json + description: JSON string of an array of file objects + archiveBase64: + type: string + format: base64 + description: Base64 encoded archive of the exercise's results + required: + - exitCode + - commit + - results + - files + - archiveBase64 + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DojoBackendResponse' + - type: object + properties: + data: + type: object + nullable: true + '401': + $ref: '#/components/responses/UNAUTHORIZED' + '404': + $ref: '#/components/responses/NOT_FOUND' + default: + $ref: '#/components/responses/ERROR' +components: + securitySchemes: + Clients_Token: + type: http + scheme: bearer + bearerFormat: JWT + ExerciseChecker_Secret: + type: apiKey + in: header + name: ExerciseSecret + parameters: + gitlabProjectIdOrNamespace: + name: gitlabProjectIdOrNamespace + description: | + The id or the namespace of the project. The namespace is the + path of the project (e.g. `dojo_project/dojo`). + in: path + required: true + schema: + type: string + assignmentNameOrUrl: + name: assignmentNameOrUrl + description: The name or the url of an assignment. + in: path + required: true + schema: + type: string + exerciseId: + name: exerciseId + description: The id of an exercise. + in: path + required: true + schema: + type: string + format: uuidv4 + schemas: + DojoBackendResponse: + type: object + properties: + timestamp: + type: string + examples: + - '1992-09-30T19:00:00.000Z' + code: + type: number + examples: + - 200 + description: + type: string + examples: + - OK + sessionToken: + type: string + examples: + - JWT token (for content, see schema named 'SessionTokenJWT') + data: + type: object + properties: { } + required: + - timestamp + - code + - description + - sessionToken + - data + User: + type: object + properties: + id: + type: number + examples: + - 142 + name: + type: string + examples: + - michael.minelli + mail: + type: string + examples: + - dojo@minelli.me + role: + type: string + enum: + - STUDENT + - TEACHING_STAFF + - ADMIN + gitlabUsername: + type: string + examples: + - michael.minelli + gitlabLastInfo: + type: object + properties: { } + externalDocs: + description: Gitlab user object + url: https://docs.gitlab.com/ee/api/users.html#list-users + isTeachingStaff: + type: boolean + examples: + - true + isAdmin: + type: boolean + examples: + - true + deleted: + type: boolean + examples: + - false + assignments: + type: array + items: + $ref: '#/components/schemas/Assignment' + exercises: + type: array + items: + $ref: '#/components/schemas/Exercise' + required: + - id + - role + - gitlabUsername + - isTeachingStaff + - isAdmin + - deleted + Assignment: + type: object + properties: + name: + type: string + examples: + - C_Hello_World + gitlabId: + type: number + examples: + - 30992 + gitlabLink: + type: string + examples: + - https://githepia.hesge.ch/dojo_project + gitlabCreationInfo: + type: object + properties: { } + externalDocs: + description: Gitlab project object + url: https://docs.gitlab.com/ee/api/projects.html#get-single-project + gitlabLastInfo: + type: object + properties: { } + externalDocs: + description: Gitlab project object + url: https://docs.gitlab.com/ee/api/projects.html#get-single-project + gitlabLastInfoDate: + type: string + examples: + - '1992-09-30 19:00:00.000' + published: + type: boolean + examples: + - true + staff: + type: array + items: + $ref: '#/components/schemas/User' + exercises: + type: array + items: + $ref: '#/components/schemas/Exercise' + required: + - name + - gitlabId + - gitlabLink + - gitlabCreationInfo + - gitlabLastInfo + - gitlabLastInfoDate + - published + - staff + - exercises + Exercise: + type: object + properties: + id: + type: string + format: uuidv4 + examples: + - eb5f2182-f5b1-42a9-80fc-cad384571053 + assignmentName: + type: string + examples: + - C_Hello_World + name: + type: string + examples: + - DojoEx - C_Hello_World - michael.minelli + gitlabId: + type: number + examples: + - 93092 + gitlabLink: + type: string + examples: + - https://githepia.hesge.ch/dojo_project/dojo + gitlabCreationInfo: + type: object + properties: { } + externalDocs: + description: Gitlab project object + url: https://docs.gitlab.com/ee/api/projects.html#get-single-project + gitlabLastInfo: + type: object + properties: { } + externalDocs: + description: Gitlab project object + url: https://docs.gitlab.com/ee/api/projects.html#get-single-project + gitlabLastInfoDate: + type: string + examples: + - '1992-09-30 19:00:00.000' + required: + - id + - assignmentName + - name + - gitlabId + - gitlabLink + - gitlabCreationInfo + - gitlabLastInfo + - gitlabLastInfoDate + DojoResult: + type: object + properties: + success: + type: boolean + examples: + - true + containerExitCode: + type: number + examples: + - 0 + successfulTests: + type: number + examples: + - 3 + failedTests: + type: number + examples: + - 0 + successfulTestsList: + type: array + items: + type: string + examples: + - [ "Test 1", "Test 2", "Test 3" ] + failedTestsList: + type: array + items: + type: string + examples: + - [ ] + required: + - success + - containerExitCode + AssignmentFile: + type: object + properties: + dojoAssignmentVersion: + type: number + examples: + - 1 + version: + type: number + examples: + - 1 + immutable: + type: array + items: + type: object + properties: + description: + type: string + examples: + - Dockerfile of the unique container + path: + type: string + examples: + - Dockerfile + isDirectory: + type: boolean + examples: + - false + required: + - path + result: + type: object + properties: + container: + type: string + examples: + - hello_world + volume: + type: string + examples: + - hello_world_volume + required: + - container + required: + - dojoAssignmentVersion + - version + - immutable + - result + SessionTokenJWT: + type: object + properties: + profile: + $ref: '#/components/schemas/User' + iat: + type: string + examples: + - '1700749215' + required: + - profile + - iat + examples: + DojoBackendResponseERROR: + value: + timestamp: '1992-09-30T19:00:00.000Z' + code: 500 + description: ERROR_MESSAGE + sessionToken: JWT token (for content, see schema named 'SessionTokenJWT') + data: null + responses: + UNAUTHORIZED: + description: UNAUTHORIZED + content: + application/json: + schema: + $ref: '#/components/schemas/DojoBackendResponse' + example: + timestamp: '1992-09-30T19:00:00.000Z' + code: 401 + description: UNAUTHORIZED + sessionToken: JWT token (for content, see schema named 'SessionTokenJWT') + data: null + NOT_FOUND: + description: NOT FOUND + content: + application/json: + schema: + $ref: '#/components/schemas/DojoBackendResponse' + example: + timestamp: '1992-09-30T19:00:00.000Z' + code: 404 + description: NOT_FOUND + sessionToken: JWT token (for content, see schema named 'SessionTokenJWT') + data: null + ERROR: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/DojoBackendResponse' + example: + timestamp: '1992-09-30T19:00:00.000Z' + code: 5XX or Internal code + description: This is an error message + sessionToken: JWT token (for content, see schema named 'SessionTokenJWT') + data: null \ No newline at end of file diff --git a/ExpressAPI/src/express/API.ts b/ExpressAPI/src/express/API.ts index 0c42647be996b34488a06411b5918c83f412fabc..21873c052d3aef71724fc9286421bf09ee336ed3 100644 --- a/ExpressAPI/src/express/API.ts +++ b/ExpressAPI/src/express/API.ts @@ -48,12 +48,12 @@ class API implements WorkerTask { private initSwagger() { const options = { swaggerOptions: { - url: '/docs/openapi.json' + url: '/docs/OpenAPI.yaml' } }; - this.backend.get('/docs/openapi.json', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/openapi.json'))); + this.backend.get('/docs/OpenAPI.yaml', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/OpenAPI/OpenAPI.yaml'))); this.backend.use('/docs/swagger', swaggerUi.serveFiles(undefined, options), swaggerUi.setup(undefined, options)); - this.backend.get('/docs/redoc.html', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/redoc.html'))); + this.backend.get('/docs/redoc.html', (req, res) => res.sendFile(path.resolve(__dirname + '/../../assets/OpenAPI/redoc.html'))); this.backend.get('/docs/', (req, res) => { res.send(` @@ -61,7 +61,7 @@ class API implements WorkerTask { <html lang="en"> <body> <ul> - <li><a href="/docs/openapi.json">OpenAPI</a></li> + <li><a href="/docs/OpenAPI.yaml">OpenAPI</a></li> <li>GUI <ul> <li><a href="/docs/swagger">Swagger</a></li> diff --git a/ExpressAPI/src/managers/GitlabManager.ts b/ExpressAPI/src/managers/GitlabManager.ts index 80ac7427fec1bf79d6e7d6b414734c286b1c021e..3fe6f6710cec46bf38f4270b0d83c05c9d076392 100644 --- a/ExpressAPI/src/managers/GitlabManager.ts +++ b/ExpressAPI/src/managers/GitlabManager.ts @@ -56,8 +56,8 @@ class GitlabManager { } } - async getRepository(idOrNamespace: string): Promise<GitlabRepository> { - const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(idOrNamespace))); + async getRepository(projectIdOrNamespace: string): Promise<GitlabRepository> { + const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(projectIdOrNamespace))); return response.data; } @@ -140,10 +140,10 @@ class GitlabManager { return response.data; } - async checkTemplateAccess(idOrNamespace: string, req: express.Request): Promise<StatusCodes> { + async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request): Promise<StatusCodes> { // Get the Gitlab project and check if it have public or internal visibility try { - const project: GitlabRepository = await this.getRepository(idOrNamespace); + const project: GitlabRepository = await this.getRepository(projectIdOrNamespace); if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) { return StatusCodes.OK; @@ -153,7 +153,7 @@ class GitlabManager { } // Check if the user and dojo are members (with at least reporter access) of the project - const members = await this.getRepositoryMembers(idOrNamespace); + const members = await this.getRepositoryMembers(projectIdOrNamespace); const isUsersAtLeastReporter = { user: false, dojo: false diff --git a/ExpressAPI/src/routes/GitlabRoutes.ts b/ExpressAPI/src/routes/GitlabRoutes.ts index e0826cb2a5896a1c37a69b6be5cb422e2ce97808..009052705080b3cebe8f9971f4f49bbac6d0d7f2 100644 --- a/ExpressAPI/src/routes/GitlabRoutes.ts +++ b/ExpressAPI/src/routes/GitlabRoutes.ts @@ -8,15 +8,13 @@ import GitlabManager from '../managers/GitlabManager'; class GitlabRoutes implements RoutesManager { registerOnBackend(backend: Express) { - backend.get('/gitlab/project/:idOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess); + backend.get('/gitlab/project/:gitlabProjectIdOrNamespace/checkTemplateAccess', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), this.checkTemplateAccess); } private async checkTemplateAccess(req: express.Request, res: express.Response) { const gitlabProjectIdOrNamespace: string = req.params.gitlabProjectIdOrNamespace; - const idOrNamespace: string = req.params.idOrNamespace; - - return res.status(await GitlabManager.checkTemplateAccess(idOrNamespace, req)).send(); + return res.status(await GitlabManager.checkTemplateAccess(gitlabProjectIdOrNamespace, req)).send(); } }