diff --git a/deadlock-plugins/deadlock-extension/package-lock.json b/deadlock-plugins/deadlock-extension/package-lock.json index 8be8152843d800e28dc25c1b207de0e7a8a22ad1..3c270bbc04235f03e3cf88aec42220db9525f8dd 100644 --- a/deadlock-plugins/deadlock-extension/package-lock.json +++ b/deadlock-plugins/deadlock-extension/package-lock.json @@ -1,18 +1,20 @@ { "name": "deadlock-coding", - "version": "0.0.2", + "version": "0.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "deadlock-coding", - "version": "0.0.2", + "version": "0.1.3", "dependencies": { "@vscode/webview-ui-toolkit": "^1.0.0", "async": "^3.2.2", + "axios": "^0.27.2", "crypto-js": "^4.1.1", "date-fns": "^2.27.0", "inversify": "^6.0.1", + "is-docker": "^3.0.0", "marked": "^4.0.6", "node-fetch": "^3.2.3", "reflect-metadata": "^0.1.13", @@ -744,8 +746,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/auto-changelog": { "version": "2.4.0", @@ -795,6 +796,28 @@ } } }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/azure-devops-node-api": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.0.tgz", @@ -1142,7 +1165,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1294,7 +1316,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -1876,6 +1897,25 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz", + "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -2297,6 +2337,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2636,7 +2690,6 @@ "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -2645,7 +2698,6 @@ "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "dependencies": { "mime-db": "1.51.0" }, @@ -4906,8 +4958,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "auto-changelog": { "version": "2.4.0", @@ -4939,6 +4990,27 @@ } } }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "azure-devops-node-api": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.0.tgz", @@ -5199,7 +5271,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -5311,8 +5382,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -5743,6 +5813,11 @@ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, + "follow-redirects": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz", + "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==" + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -6053,6 +6128,11 @@ "has": "^1.0.3" } }, + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6319,14 +6399,12 @@ "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "requires": { "mime-db": "1.51.0" } diff --git a/deadlock-plugins/deadlock-extension/package.json b/deadlock-plugins/deadlock-extension/package.json index 5095eb4f65f5a4615f88dfc26eb316ec136a1c78..244410622594d36397df48bd43afb174d3390b2e 100644 --- a/deadlock-plugins/deadlock-extension/package.json +++ b/deadlock-plugins/deadlock-extension/package.json @@ -1,7 +1,7 @@ { "name": "deadlock-coding", "description": "Deadlock Coding", - "version": "0.0.2", + "version": "0.1.3", "publisher": "Deadlock", "icon": "media/logo.png", "engines": { @@ -97,9 +97,11 @@ "dependencies": { "@vscode/webview-ui-toolkit": "^1.0.0", "async": "^3.2.2", + "axios": "^0.27.2", "crypto-js": "^4.1.1", "date-fns": "^2.27.0", "inversify": "^6.0.1", + "is-docker": "^3.0.0", "marked": "^4.0.6", "node-fetch": "^3.2.3", "reflect-metadata": "^0.1.13", diff --git a/deadlock-plugins/deadlock-extension/src/config.prod.ts b/deadlock-plugins/deadlock-extension/src/config.prod.ts index c5b24b4bb464218cdaa63059ef13cfa0daf8a5ec..93648f3f7cd18f77b2e7c37fbf2f45773ea809fc 100644 --- a/deadlock-plugins/deadlock-extension/src/config.prod.ts +++ b/deadlock-plugins/deadlock-extension/src/config.prod.ts @@ -4,7 +4,9 @@ export const KEYCLOAK_TOKEN_CREATE_URL = 'https://auth.deadlock.io/auth/realms/D export const KEYCLOAK_USER_INFO_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo'; export const REGISTRY_MISSION_URL = 'registry.e-biz.fr/deadlock/deadlock-challenges'; export const REJECT_UNAUTHORIZED = true; -export const ENABLE_HTTP_SERVER = false; export const ENABLE_AUTOMATIC_SAVE = true; +export const ENABLE_RECORDER_HTTP_SERVER = false; export const RECORDER_HTTP_SERVER_PORT = 8751; export const RECORDER_HTTP_SERVER_URL = `http://localhost:${RECORDER_HTTP_SERVER_PORT}`; +export const API_URL = 'https://api.deadlock.io/'; +export const API_QUERY_REFERER = 'https://takima.deadlock.io'; diff --git a/deadlock-plugins/deadlock-extension/src/config.staging.ts b/deadlock-plugins/deadlock-extension/src/config.staging.ts index 3c310bb696d029091ec7706a03c222286c44d883..4067193b509cb6c7e8210b9ad9f2ac054a51b1c3 100644 --- a/deadlock-plugins/deadlock-extension/src/config.staging.ts +++ b/deadlock-plugins/deadlock-extension/src/config.staging.ts @@ -6,6 +6,9 @@ export const KEYCLOAK_USER_INFO_URL = 'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo'; export const REGISTRY_MISSION_URL = 'registry.e-biz.fr/deadlock/deadlock-challenges'; export const REJECT_UNAUTHORIZED = true; -export const ENABLE_HTTP_SERVER = false; -export const HTTP_SERVER_PORT = 8751; -export const HTTP_SERVER_URL = `http://localhost:${HTTP_SERVER_PORT}`; +export const ENABLE_AUTOMATIC_SAVE = true; +export const ENABLE_RECORDER_HTTP_SERVER = false; +export const RECORDER_HTTP_SERVER_PORT = 8751; +export const RECORDER_HTTP_SERVER_URL = `http://localhost:${RECORDER_HTTP_SERVER_PORT}`; +export const API_URL = 'https://api.staging.deadlock.io/'; +export const API_QUERY_REFERER = 'https://takima.staging.deadlock.io'; diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts index 99c5026e954acd14aaa48a0eecb1d7c63a699725..850e35d0d6efc642905ba5ea30da33904c823cd0 100644 --- a/deadlock-plugins/deadlock-extension/src/config.ts +++ b/deadlock-plugins/deadlock-extension/src/config.ts @@ -6,7 +6,9 @@ export const KEYCLOAK_USER_INFO_URL = 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo'; export const REGISTRY_MISSION_URL = 'registry.e-biz.fr/deadlock/deadlock-challenges'; export const REJECT_UNAUTHORIZED = false; -export const ENABLE_HTTP_SERVER = false; export const ENABLE_AUTOMATIC_SAVE = true; -export const HTTP_SERVER_PORT = 8751; -export const HTTP_SERVER_URL = `http://localhost:${HTTP_SERVER_PORT}`; +export const ENABLE_RECORDER_HTTP_SERVER = false; +export const RECORDER_HTTP_SERVER_PORT = 8751; +export const RECORDER_HTTP_SERVER_URL = `http://localhost:${RECORDER_HTTP_SERVER_PORT}`; +export const API_URL = 'https://api.dev.deadlock.io/'; +export const API_QUERY_REFERER = 'https://takima.dev.deadlock.io'; diff --git a/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts b/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..3cefbb85e009f052d8331a1f3a7ba654a67582ea --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts @@ -0,0 +1,96 @@ +import { GiteaPublicProperties } from './../customTypings/giteaPublicProperties.model'; +import { SshKeyPair } from './../customTypings/sshKeyPair.model'; +import axios, { AxiosInstance } from 'axios'; +import { API_URL, API_QUERY_REFERER } from '../config'; +import { User } from '../customTypings/user.model'; +import Controller from './controller'; +import ExtensionStore from './extensionStore'; +import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection'; + +export default class CallApiService { + private callApi: AxiosInstance; + constructor(private keycloackConnection: KeycloakOAuth2DeviceFlowConnection, private extensionStore: ExtensionStore) { + this.callApi = axios.create({ + baseURL: API_URL, + headers: { + 'Content-Type': 'application/json', + referer: API_QUERY_REFERER, + }, + }); + + this.initApiInterceptor(); + } + + initApiInterceptor() { + this.callApi.interceptors.request.use( + async (config) => { + const accessToken = await this.extensionStore.getAccessToken(); + + if (accessToken && config.headers) { + config.headers['authorization'] = `BEARER ${accessToken}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + }, + ); + + this.callApi.interceptors.response.use( + (res) => { + return res; + }, + async (err) => { + const originalConfig = err.config; + if (err.response) { + // Access Token was expired + if (err.response.status === 401) { + if (!originalConfig._retry) { + originalConfig._retry = true; + try { + const storedRefreshToken = await this.extensionStore.getRefreshToken(); + const { accessToken } = await this.keycloackConnection.getToken({ + refreshToken: storedRefreshToken, + openLink: Controller.openBrowserWithUrl, + }); + + this.callApi.defaults.headers.common['authorization'] = `BEARER ${accessToken}`; + return this.callApi(originalConfig); + } catch (_error: any) { + if (_error.response && _error.response.data) { + return Promise.reject(_error.response.data); + } + return Promise.reject(_error); + } + } else if (originalConfig._retry) { + // IF REFRESH TOKEN NOT WORK, REQUEST NEW CONNECTION IN USER BROWSER + try { + await this.keycloackConnection.getToken({ openLink: Controller.openBrowserWithUrl }); + return this.callApi(originalConfig); + } catch (_error) { + return Promise.reject(_error); + } + } + } + + if (err.response.status === 403 && err.response.data) { + return Promise.reject(err.response.data); + } + } + return Promise.reject(err); + }, + ); + } + + getGiteaPublicProperties(): Promise<GiteaPublicProperties> { + return this.callApi.get<GiteaPublicProperties>(`gitea`).then((res) => res.data); + } + + getUserSshKey(): Promise<SshKeyPair> { + return this.callApi.put<SshKeyPair>(`users/gitea/keypair`).then((res) => res.data); + } + + getUser(): Promise<User> { + return this.callApi.get<User>(`auth`).then((res) => res.data); + } +} diff --git a/deadlock-plugins/deadlock-extension/src/core/config.ts b/deadlock-plugins/deadlock-extension/src/core/config.ts index 7222a295001d60f5bb9c7068efd7b7ecb324a3e5..9cb8eb8311da779c8cbf734799a6655fa41cdb8a 100644 --- a/deadlock-plugins/deadlock-extension/src/core/config.ts +++ b/deadlock-plugins/deadlock-extension/src/core/config.ts @@ -1,9 +1,10 @@ import * as os from 'os'; import * as path from 'path'; +import isDocker from 'is-docker'; const homeDir = os.homedir(); // if we are on container, means the directory will depend differently -const onContainer = homeDir.includes('theia') || homeDir.includes('root') || homeDir.includes('deadlock'); +const onContainer = isDocker(); const deadlockExtensionPath = path.join(homeDir, 'deadlock-extension'); diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts index 633a17536ecfa99be367f8c88919e94dd9ce098b..17339d057563e3836f55ca99cf617ca06d4658fa 100644 --- a/deadlock-plugins/deadlock-extension/src/core/controller.ts +++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts @@ -1,3 +1,5 @@ +import { GiteaPublicProperties } from './../customTypings/giteaPublicProperties.model'; +import { User } from './../customTypings/user.model'; import * as vscode from 'vscode'; import { KEYCLOAK_DEVICE_AUTH_URL, @@ -13,25 +15,32 @@ import { CHOOSE_MISSION_WORKDIR_COMMAND, CommandHandler, OPEN_URL_IN_BROWSER_COM import ExtensionStore from './extensionStore'; import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection'; import Mission from './mission'; - +import CallApiService from './callApi.service'; +import KeycloakOAuth2DeviceFlowConnectionVSCodeImpl from './keycloakOAuth2DeviceFlowConnectionVSCodeImpl'; +import SshKeyManager from './sshKeyManager'; export default class Controller { public connection: KeycloakOAuth2DeviceFlowConnection; private commandHandler: CommandHandler; private briefingView: BriefingView; private quickSetupView: QuickSetupView; private extensionStore: ExtensionStore; + private callApiService: CallApiService; + private sshKeyManager: SshKeyManager; constructor(private context: vscode.ExtensionContext) { this.extensionStore = ExtensionStore.getInstance(context); this.briefingView = new BriefingView(); this.quickSetupView = new QuickSetupView(context.extensionUri); this.commandHandler = new CommandHandler(this); - this.connection = new KeycloakOAuth2DeviceFlowConnection( + this.connection = new KeycloakOAuth2DeviceFlowConnectionVSCodeImpl( KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL, ); + this.callApiService = new CallApiService(this.connection, this.extensionStore); + this.sshKeyManager = new SshKeyManager(); + this.init(); } private async init() { @@ -72,9 +81,18 @@ export default class Controller { } this.chooseMissionWorkdir(); } else { + await this.insertSshKeyInMissionWorkdir(folderUri[0].path); this.extensionStore.setMissionWorkdir(folderUri[0].path); } } + + public async insertSshKeyInMissionWorkdir(sshKeyFolderPath) { + const { publicKey, privateKey } = await this.callApiService.getUserSshKey(); + this.sshKeyManager.createSshKeyFiles(publicKey, privateKey, `${sshKeyFolderPath}/.ssh`); + } + + public async createMissionUserChallengeJson(missionId: string) {} + public async clear() { const exensionStorage = ExtensionStore.getInstance(); await exensionStorage.clear(); @@ -95,6 +113,7 @@ export default class Controller { } public async launchMission(missionId: string | null, missionVersion: string | null) { + console.log('LAUCH MISSION : ' + missionId); if (missionId && missionVersion) { vscode.window.showInformationMessage(`vous lancez la mission ${missionId}`); const hadMissionWorkdir = this.extensionStore.getMissionWorkdir() !== undefined; @@ -111,16 +130,26 @@ export default class Controller { vscode.window.showInformationMessage('Déjà connecté: session récupérée'); } + console.log('BEFORE QUERY'); + const user: User = await this.callApiService.getUser(); + const giteaPublicProperties: GiteaPublicProperties = await this.callApiService.getGiteaPublicProperties(); // TODO Should I fetch GET api/missions/{missionId} one day instead of passing necessary parameters in vscode xdg-open link ? - const mission = new Mission({ - registryBaseURL: REGISTRY_MISSION_URL, - missionId: missionId, - missionVersion: missionVersion, - }); + const mission = new Mission( + { + registryBaseURL: REGISTRY_MISSION_URL, + missionId: missionId, + missionVersion: missionVersion, + }, + user, + giteaPublicProperties, + ); vscode.window.showInformationMessage( 'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId, ); + console.log('BEFORE SETUP'); await mission.setup({}); + console.log('BEFORE open editor'); + await mission.openEditorInFolder(); vscode.commands.executeCommand(OPEN_QUICK_SETUP_COMMAND.cmd); diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts index e0fbb38bf3c481e4ddafe1262b2d0b40c281981e..1ec89b2ed0773812b90ef631506324c0943d9b8a 100644 --- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts +++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts @@ -65,7 +65,7 @@ export default class KeycloakOAuth2DeviceFlowConnection { } } - public async getToken(args: { refreshToken?: string; openLink: (link: string) => void }) { + public async getToken(args: { refreshToken?: string; openLink?: (link: string) => void }) { const { refreshToken, openLink } = args; if (!!refreshToken) { await this.createUserAuthentication({ @@ -85,6 +85,7 @@ export default class KeycloakOAuth2DeviceFlowConnection { await this.registerDevice(); } try { + if (!openLink) throw new Error('You neeed to provide a way to open a link for oauth device flow protocol'); openLink(this.deviceAuthorizationRequestResponse.verification_uri_complete!); await this.createUserAuthentication({ url: this.tokenUrl, diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnectionVSCodeImpl.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnectionVSCodeImpl.ts new file mode 100644 index 0000000000000000000000000000000000000000..fabccd3adc494949706d6f0016b7e5d8549645ab --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnectionVSCodeImpl.ts @@ -0,0 +1,28 @@ +import ExtensionStore from './extensionStore'; +import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection'; + +export default class KeycloakOAuth2DeviceFlowConnectionVSCodeImpl extends KeycloakOAuth2DeviceFlowConnection { + private extensionStore: ExtensionStore; + constructor(deviceUrl: string, tokenUrl: string, userInfoUrl: string) { + super(deviceUrl, tokenUrl, userInfoUrl); + this.extensionStore = ExtensionStore.getInstance(); + } + + public async getToken(args: { refreshToken?: string; openLink: (link: string) => void }): Promise<{ + accessToken: string; + refreshToken: string; + }> { + const existingAccessToken = await this.extensionStore.getAccessToken(); + const existingRefreshToken = await this.extensionStore.getRefreshToken(); + if (existingAccessToken!! && existingRefreshToken!! && (await super.tokenIsValid(existingAccessToken!))) { + return Promise.resolve({ accessToken: existingAccessToken, refreshToken: existingRefreshToken }); + } + const { refreshToken, accessToken } = await super.getToken({ + refreshToken: args.refreshToken, + openLink: args.openLink, + }); + await this.extensionStore.setAccessToken(accessToken); + await this.extensionStore.setRefreshToken(refreshToken); + return Promise.resolve({ refreshToken: refreshToken, accessToken: accessToken }); + } +} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission.ts index 8a4d7118406b8bd22ed53805ce13ef62d7d07f58..d159354d6a1658ad89c2063ed6a67e47d0e98b4e 100644 --- a/deadlock-plugins/deadlock-extension/src/core/mission.ts +++ b/deadlock-plugins/deadlock-extension/src/core/mission.ts @@ -1,9 +1,11 @@ +import { GiteaPublicProperties } from './../customTypings/giteaPublicProperties.model'; import { exec as _exec } from 'child_process'; import * as fs from 'fs'; import * as util from 'util'; import * as vscode from 'vscode'; import { error as err, log } from '../recorder/utils'; import ExtensionStore from './extensionStore'; +import { User, UserChallengeJson } from '../customTypings/user.model'; /** * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback} @@ -22,16 +24,19 @@ export default class Mission { private readonly remoteGiteaWorkDir: string; private readonly userConfigPath: string; private readonly userSshPath: string; + private readonly user: User; + private readonly giteaPublicProperties: GiteaPublicProperties; + constructor( private params: { registryBaseURL: string; missionId: string; missionVersion: string }, - userSshPath: string, + user: User, + giteaPublicProperties: GiteaPublicProperties, ) { const { registryBaseURL, missionId, missionVersion } = params; this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? ''; this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`; - // TODO: NEED TO BE MOCK FOR MOMENT : WAITING US#8 - this.userConfigPath = '/home/lansana/Documents/deadlock/deadlock-theia/config'; //`${this.hostMissionDir}/.userConfig`; - this.userSshPath = '/home/lansana/Documents/deadlock/deadlock-theia/ssh'; // userSshPath + this.userConfigPath = `${this.hostMissionDir}/.config`; + this.userSshPath = `${this.hostBaseWorkDir}/.ssh`; this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`; this.hostMissionDevcontainerFileDir = `${this.hostMissionDevcontainerDir}/devcontainer.json`; this.hostMissionMountDir = `${this.hostMissionDir}/mounted`; @@ -39,6 +44,8 @@ export default class Mission { this.remoteUserHomeDir = '/home/deadlock'; this.remoteMissionDir = `${this.remoteUserHomeDir}/mission`; this.remoteGiteaWorkDir = `/src`; + this.user = user; + this.giteaPublicProperties = giteaPublicProperties; } static async pullImage(url) { @@ -50,7 +57,13 @@ export default class Mission { public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true }); await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true }); - await fs.promises.writeFile( + await fs.promises.mkdir(this.userConfigPath, { recursive: true }); + await this.createDevContainerFile(options); + await this.createUserChallengeJsonFile(); + } + + private createDevContainerFile(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { + return fs.promises.writeFile( this.hostMissionDevcontainerFileDir, (() => { const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = { @@ -90,6 +103,25 @@ export default class Mission { ); } + private createUserChallengeJsonFile() { + return fs.promises.writeFile( + `${this.userConfigPath}/user-challenge.json`, + (() => { + const userChallengeJson: UserChallengeJson = { + giteaHost: this.giteaPublicProperties.sshHost, + giteaSshPort: this.giteaPublicProperties.sshPort, + username: this.user.id.split('-').join(''), + email: `${this.user.id.split('-').join('')}@deadlock.io`, + missionId: this.params.missionId, + remoteGitUsername: this.user.id.split('-').join(''), + currentUserDetails: this.user.details, + remoteUserDetails: this.user.details, + }; + return JSON.stringify(userChallengeJson, null, 2); + })(), + ); + } + public async openEditorInFolder(arbitraryPath?: string) { if (arbitraryPath) { return vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(arbitraryPath)); diff --git a/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ebf3a59b348ca58631e85c69ab1ba3df9dc8031 --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts @@ -0,0 +1,22 @@ +import ExtensionStore from './extensionStore'; +import * as fs from 'fs'; + +export default class SshKeyManager { + constructor() {} + + public async createSshKeyFiles(publicKey: string, privateKey: string, sshKeyFolderPath: string) { + await this.createSshKeyFolderIfNotExist(sshKeyFolderPath); + await fs.promises.writeFile(`${sshKeyFolderPath}/id_rsa.pub`, publicKey); + + await fs.promises.writeFile(`${sshKeyFolderPath}/id_rsa`, privateKey, { mode: 0o600 }); + } + private async createSshKeyFolderIfNotExist(sshKeyFolderPath) { + if (!this.isSshKeyFolderExist(sshKeyFolderPath)) { + await fs.promises.mkdir(sshKeyFolderPath, { recursive: true }); + } + } + + private isSshKeyFolderExist(sshKeyFolderPath: string) { + return fs.existsSync(sshKeyFolderPath); + } +} diff --git a/deadlock-plugins/deadlock-extension/src/core/userConfig.ts b/deadlock-plugins/deadlock-extension/src/core/userConfig.ts index da50d104cf84ef2e34792ee93f17e91c5d352e51..0b6906cfc0b95c932a24b5b52e575bf925602943 100644 --- a/deadlock-plugins/deadlock-extension/src/core/userConfig.ts +++ b/deadlock-plugins/deadlock-extension/src/core/userConfig.ts @@ -17,13 +17,7 @@ import { log } from './../recorder/utils'; */ import { error } from '../recorder/utils'; - -export interface UserDetails { - firstName: string; - lastName: string; - avatarUrl: string; - email: string; -} +import { UserDetails } from '../customTypings/user.model'; export default abstract class UserConfig { private userConfigJson: any | undefined; diff --git a/deadlock-plugins/deadlock-extension/src/model/giteaPublicProperties.model.ts b/deadlock-plugins/deadlock-extension/src/model/giteaPublicProperties.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c10aba3fd8e3a24e20ae78b992e23e06a43b29b --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/model/giteaPublicProperties.model.ts @@ -0,0 +1,6 @@ +export interface GiteaPublicProperties { + apiHost: string; + sshHost: string; + port: number; + sshPort: number; +} diff --git a/deadlock-plugins/deadlock-extension/src/model/sshKeyPair.model.ts b/deadlock-plugins/deadlock-extension/src/model/sshKeyPair.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..4e1868d587295b566eca654b3aa06a168e971345 --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/model/sshKeyPair.model.ts @@ -0,0 +1,4 @@ +export interface SshKeyPair { + privateKey: string; + publicKey: string; +} diff --git a/deadlock-plugins/deadlock-extension/src/model/user.model.ts b/deadlock-plugins/deadlock-extension/src/model/user.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3e1b03e91b32d58e85f07df898e8f50b554020e --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/model/user.model.ts @@ -0,0 +1,25 @@ +export interface User { + id: string; + details: UserDetails; +} + +export interface UserDetails { + firstName: string; + lastName: string; + organization: string; + email: string; + login: string; + roles: string[]; + avatarUrl: string; +} + +export interface UserChallengeJson { + giteaHost: string; + giteaSshPort: number; + username: string; + email: string; + missionId: string; + remoteGitUsername: string; + currentUserDetails: UserDetails; + remoteUserDetails: UserDetails; +} diff --git a/deadlock-plugins/deadlock-extension/src/recorder/index.ts b/deadlock-plugins/deadlock-extension/src/recorder/index.ts index 6e10a2b8208a669439ce221f9273b1a79dcf131a..b513b15bc3f093fe9c68617c56925c6d0326c005 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/index.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/index.ts @@ -7,7 +7,7 @@ import { PROJECT_SRC_PATH, PROJECT_DEADLOCK_DESKTOP_PATH } from '../core/config' import { copyProjectSources, clearFilesExceptGit, log, error, renameTempToUserGitFiles } from './utils'; import UserConfig from '../core/userConfig'; import HttpServer from './services/http-server'; -import { ENABLE_HTTP_SERVER } from '../config'; +import { ENABLE_RECORDER_HTTP_SERVER } from '../config'; import FileWatcher from './services/file-watcher'; import AutomaticSave from './services/automatic-save'; export default class Recorder { @@ -63,7 +63,7 @@ export default class Recorder { log('Init GitMission'); gitMission = await new GitMission(userConfig).init(); - if (ENABLE_HTTP_SERVER) new HttpServer(gitMission); + if (ENABLE_RECORDER_HTTP_SERVER) new HttpServer(gitMission); await this.setupFromRemoteRepo(gitMission); if (ENABLE_AUTOMATIC_SAVE) new AutomaticSave(PROJECT_DEADLOCK_DESKTOP_PATH, gitMission); diff --git a/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts b/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts index ab7a5d5b106be9dce73919780f00a704d960ecba..99623646ff5e937d46a87fee30abbc146254ba24 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/services/http-server.ts @@ -1,7 +1,7 @@ import GitMission from '../../core/gitMission'; import { Express } from 'express'; import { updateRemote, CommitFrom, log } from '../utils'; -import { HTTP_SERVER_PORT } from '../../config'; +import { RECORDER_HTTP_SERVER_PORT } from '../../config'; const express = require('express'); export default class HttpServer { app: Express; @@ -30,8 +30,8 @@ export default class HttpServer { } listen() { - this.app.listen(HTTP_SERVER_PORT, () => { - log(`Http server listen on port ${HTTP_SERVER_PORT}`); + this.app.listen(RECORDER_HTTP_SERVER_PORT, () => { + log(`Http server listen on port ${RECORDER_HTTP_SERVER_PORT}`); }); } } diff --git a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts index dcda0382dfa635af4dba02c4350fc82e5fb1c55b..5b8585bb99de212ecd8867f3eeb5e5243144f1f4 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/utils.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/utils.ts @@ -4,6 +4,8 @@ import { PROJECT_SRC_PATH, PROJECT_DEADLOCK_DESKTOP_PATH } from '../core/config' import { format } from 'date-fns'; import { execSync } from 'child_process'; import { existsSync, renameSync, copyFileSync, PathLike } from 'fs'; +import KeycloakOAuth2DeviceFlowConnection from '../core/keycloakOAuth2DeviceFlowConnection'; +import ExtensionStore from '../core/extensionStore'; const util = require('util'); const unlink = util.promisify(require('fs').unlink);