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 { 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 express from 'express'; import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; import SharedConfig from '../shared/config/SharedConfig'; import GitlabProfile from '../shared/types/Gitlab/GitlabProfile'; class GitlabManager { 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; } public async getUserById(id: number): Promise<GitlabUser | undefined> { try { const params: any = {}; const user = (await axios.get<GitlabUser>(`${ this.getApiUrl(GitlabRoute.USERS_GET) }/${ String(id) }`, { params: params })).data; return user.id === id ? user : undefined; } catch ( e ) { } return undefined; } public async getUserByUsername(username: string): Promise<GitlabUser | undefined> { try { const params: any = {}; 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; } catch ( e ) { } return undefined; } async getRepository(idOrNamespace: string): Promise<GitlabRepository> { const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoute.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(idOrNamespace))); 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 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), { name : name, description : description, import_url : import_url, initialize_with_readme: initializeWithReadme, namespace_id : namespace, shared_runners_enabled: sharedRunnersEnabled, visibility : visibility, wiki_enabled : wikiEnabled }); return response.data; } async deleteRepository(repoId: number): Promise<void> { return await axios.delete(this.getApiUrl(GitlabRoute.REPOSITORY_DELETE).replace('{{id}}', String(repoId))); } 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)), { name : name, path : path, description : description, namespace_id: namespace, visibility : visibility }); return response.data; } 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); return response.data; } async changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<GitlabRepository> { return await this.editRepository(repoId, { visibility: visibility.toString() }); } async addRepositoryMember(repoId: number, userId: number, accessLevel: GitlabAccessLevel): Promise<GitlabMember> { const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoute.REPOSITORY_MEMBER_ADD).replace('{{id}}', String(repoId)), { user_id : userId, access_level: accessLevel }); return response.data; } 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, protected : isProtected, masked : isMasked }); return response.data; } 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, name : name }); return response.data; } async checkTemplateAccess(idOrNamespace: 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); if ( [ GitlabVisibility.PUBLIC.valueOf(), GitlabVisibility.INTERNAL.valueOf() ].includes(project.visibility) ) { return StatusCodes.OK; } } catch ( e ) { return StatusCodes.NOT_FOUND; } // Check if the user and dojo are members (with at least reporter access) of the project const members = await this.getRepositoryMembers(idOrNamespace); const isUsersAtLeastReporter = { user: false, dojo: false }; members.forEach(member => { if ( member.access_level >= GitlabAccessLevel.REPORTER ) { if ( member.id === req.session.profile.id ) { isUsersAtLeastReporter.user = true; } else if ( member.id === Config.gitlab.account.id ) { isUsersAtLeastReporter.dojo = true; } } }); return isUsersAtLeastReporter.user && isUsersAtLeastReporter.dojo ? StatusCodes.OK : StatusCodes.UNAUTHORIZED; } 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() }); return response.data; } async getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<GitlabTreeFile>> { let address: string | undefined = this.getApiUrl(GitlabRoute.REPOSITORY_TREE).replace('{{id}}', String(repoId)); let params: any = { pagination: 'keyset', recursive : recursive, per_page : 100, ref : branch }; let 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; } } 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 } }); 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; await axiosFunction(this.getApiUrl(GitlabRoute.REPOSITORY_FILE).replace('{{id}}', String(repoId)).replace('{{filePath}}', encodeURIComponent(filePath)), { encoding : 'base64', branch : branch, commit_message: commitMessage, content : fileBase64, author_name : authorName, author_email : authorMail }); } async createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined) { 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) { return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail); } } export default new GitlabManager();