From e752c1da1c99661c02e8c9150d9f746c3b15fcdd Mon Sep 17 00:00:00 2001
From: Sandro Weber <webers@in.tum.de>
Date: Tue, 12 Oct 2021 14:34:01 +0200
Subject: [PATCH] updated with newest changes form old frontend

---
 public/index.html                             |   1 +
 src/services/authentication-service-v2.js     | 276 ++++++++++--------
 .../files/experiment-storage-service.js       |   2 -
 src/services/http-service.js                  |   9 +-
 src/services/proxy/nrp-user-service.js        |   1 -
 src/services/roslib-service.js                |   4 +-
 6 files changed, 161 insertions(+), 132 deletions(-)

diff --git a/public/index.html b/public/index.html
index dd17215..7f0865c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -28,6 +28,7 @@
     <title>Neurorobotics Platform</title>
   </head>
   <body>
+    <script src="https://iam.ebrains.eu/auth/js/keycloak.js"></script>
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root"></div>
     <!--
diff --git a/src/services/authentication-service-v2.js b/src/services/authentication-service-v2.js
index 85ff46f..11e4069 100644
--- a/src/services/authentication-service-v2.js
+++ b/src/services/authentication-service-v2.js
@@ -1,5 +1,12 @@
 import config from '../config.json';
 
+/* global Keycloak */
+
+let keycloakClient = undefined;
+
+const INIT_CHECK_INTERVAL_MS = 100;
+const INIT_CHECK_MAX_RETRIES = 10;
+
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
 
@@ -12,13 +19,13 @@ class AuthenticationService {
       throw new Error('Use ' + this.constructor.name + '.instance');
     }
 
-    this.CLIENT_ID = config.authV2.clientId;
-    this.CLIENT_SECRET = config.authV2.secret;
-    this.STORAGE_KEY = `tokens-${this.CLIENT_ID}@https://iam.ebrains.eu/auth/realms/hbp/protocol/openid-connect/auth`;
+    this.proxyURL = config.api.proxy.url;
+    this.oidcEnabled = config.auth.enableOIDC;
+    this.clientId = config.auth.clientId;
+    this.authURL = config.auth.url;
+    this.STORAGE_KEY = `tokens-${this.clientId}@https://iam.ebrains.eu/auth/realms/hbp`;
 
-    this.redirectToAuthPage = true;
-    this.checkForSessionStateAndAuthCode();
-    //this.checkForNewTokenToStore();
+    this.init();
   }
 
   static get instance() {
@@ -29,120 +36,126 @@ class AuthenticationService {
     return _instance;
   }
 
-  /**
-   * Checks if the current page URL contains access tokens.
-   * This happens when the successfully logging in at the proxy login page and
-   * being redirected back with the token info.
-   * Will automatically remove additional access info and present a clean URL after being redirected.
-   */
-  checkForNewTokenToStore() {
-    const path = window.location.href;
-    const accessTokenMatch = /&access_token=([^&]*)/.exec(path);
+  init() {
+    this.initialized = false;
+    if (this.oidcEnabled) {
+      this.authCollab().then(() => {
+        this.initialized = true;
+      });
+    }
+    else {
+      this.checkForNewLocalTokenToStore();
+      this.initialized = true;
+    }
 
-    if (!accessTokenMatch || !accessTokenMatch[1]) {
-      return;
+    this.promiseInitialized = new Promise((resolve, reject) => {
+      let numChecks = 0;
+      let checkInterval = setInterval(() => {
+        numChecks++;
+        if (numChecks > INIT_CHECK_MAX_RETRIES) {
+          clearInterval(checkInterval);
+          reject();
+        }
+        if (this.initialized) {
+          clearInterval(checkInterval);
+          resolve();
+        }
+      }, INIT_CHECK_INTERVAL_MS);
+    });
+  }
+
+  authenticate(config) {
+    if (this.oidcEnabled) {
+      this.authCollab(config);
     }
+    else {
+      this.authLocal(config);
+    }
+  }
 
-    let accessToken = accessTokenMatch[1];
+  getToken() {
+    if (this.oidcEnabled) {
+      if (keycloakClient && keycloakClient.authenticated) {
+        keycloakClient
+          .updateToken(30)
+          .then(function() {})
+          .catch(function() {
+            console.error('Failed to refresh token');
+          });
+        return keycloakClient.token;
+      }
+      else {
+        console.error('getToken() - Client is not authenticated');
+      }
+    }
+    else {
+      return this.getStoredLocalToken();
+    }
+  }
 
-    localStorage.setItem(
-      this.STORAGE_KEY,
-      //eslint-disable-next-line camelcase
-      JSON.stringify([{ access_token: accessToken }])
-    );
-    const pathMinusAccessToken = path.substr(0, path.indexOf('&access_token='));
-    window.location.href = pathMinusAccessToken;
+  logout() {
+    if (this.oidcEnabled) {
+      if (keycloakClient && keycloakClient.authenticated) {
+        keycloakClient.logout();
+        keycloakClient.clearStoredLocalToken();
+      }
+      else {
+        console.error('Client is not authenticated');
+      }
+    }
+    else {
+      return this.clearStoredLocalToken();
+    }
   }
 
-  /**
-   * Checks if the current page URL contains access tokens.
-   * This happens when the successfully logging in at the proxy login page and
-   * being redirected back with the token info.
-   * Will automatically remove additional access info and present a clean URL after being redirected.
-   */
-  checkForSessionStateAndAuthCode() {
-    const path = window.location.href;
-    const sessionStateMatch = /session_state=([^&]*)/.exec(path);
-    const authCodeMatch = /code=([^&]*)/.exec(path);
-
-    if (!sessionStateMatch || !authCodeMatch) {
+  authLocal(config) {
+    if (this.authenticating) {
       return;
     }
+    this.authenticating = true;
 
-    this.redirectToAuthPage = false;
+    this.authURL = this.authURL || config.url;
+    this.clientId = this.clientId || config.clientId;
 
-    let sessionState = sessionStateMatch[1];
-    let authCode = authCodeMatch[1];
-    console.info('authCode = ' + authCode);
+    let absoluteUrl = /^https?:\/\//i;
+    if (!absoluteUrl.test(this.authURL)) {
+      this.authURL = `${this.proxyURL}${this.authURL}`;
+    }
 
-    this.getAccessToken(authCode);
+    this.clearStoredLocalToken();
+    window.location.href = `${this.authURL}&client_id=${this
+      .clientId}&redirect_uri=${encodeURIComponent(window.location.href)}`;
   }
 
-  async getAccessToken(authorizationCode) {
-    console.info('getAccessToken - origin = ' + window.location.origin);
-    console.info('getAccessToken - authenticationCode = ' + authorizationCode);
-    let urlRequestAccessToken = 'https://iam.ebrains.eu/auth/realms/hbp/protocol/openid-connect/token';
-
-    let options = {
-      method: 'POST',
-      mode: 'cors', // no-cors, *cors, same-origin
-      //cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
-      //credentials: 'same-origin', // include, *same-origin, omit
-      headers: {
-        'Content-Type': 'application/x-www-form-urlencoded'//,
-        //'Access-Control-Allow-Origin': '*',
-        //Referer: window.location.origin
-      },
-      // redirect: manual, *follow, error
-      //redirect: 'follow',
-      // referrerPolicy: no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin,
-      // strict-origin, strict-origin-when-cross-origin, unsafe-url
-      referrerPolicy: 'no-referrer'
-    };
-
-    let formDetails = {
-      grant_type: 'authorization_code',
-      client_id: this.CLIENT_ID,
-      redirect_uri: window.location.origin,
-      client_secret: this.CLIENT_SECRET,
-      code: authorizationCode
-    };
-    const formBody = Object.entries(formDetails)
-      .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
-      .join('&');
-    options.body = formBody;
-
-    const responseAccessTokenRequest = await fetch(urlRequestAccessToken, options);
-    const responseAccessTokenRequestJSON = await responseAccessTokenRequest.json();
-    console.info(responseAccessTokenRequest);
-    console.info(responseAccessTokenRequestJSON);
-
-    /*localStorage.setItem(
+  checkForNewLocalTokenToStore() {
+    const path = window.location.pathname;
+
+    const accessTokenMatch = /&access_token=([^&]*)/.exec(path);
+    if (!accessTokenMatch || !accessTokenMatch[1]) {
+      return;
+    }
+
+    let accessToken = accessTokenMatch[1];
+    localStorage.setItem(
       this.STORAGE_KEY,
       //eslint-disable-next-line camelcase
       JSON.stringify([{ access_token: accessToken }])
-    );*/
-    //const pathMinusAccessToken = path.substr(0, path.indexOf('?'));
-    //window.location.href = pathMinusAccessToken;
+    );
+
+    // navigate to clean url
+    let cleanedPath = path.substr(0, path.indexOf('&'));
+    window.location = cleanedPath;
   }
 
-  /**
-   * Clear currently stored access token.
-   */
-  clearStoredToken() {
+  clearStoredLocalToken() {
     localStorage.removeItem(this.STORAGE_KEY);
   }
 
-  /**
-   * Get the stored access token.
-   *
-   * @return token The stored access token. Or strings identifying 'no-token' / 'malformed-token'.
-   */
-  getStoredToken() {
+  getStoredLocalToken() {
     let storedItem = localStorage.getItem(this.STORAGE_KEY);
     if (!storedItem) {
       // this token will be rejected by the server and the client will get a proper auth error
-      return AuthenticationService.CONSTANTS.NO_TOKEN;
+      return 'no-token';
     }
 
     try {
@@ -151,38 +164,57 @@ class AuthenticationService {
     }
     catch (e) {
       // this token will be rejected by the server and the client will get a proper auth error
-      return AuthenticationService.CONSTANTS.MALFORMED_TOKEN;
+      return 'malformed-token';
     }
   }
 
-  /**
-   * Opens the proxy's authentication page.
-   *
-   * @param {*} url The URL of the authentication page.
-   * If not an absolute URL it is assumed to be a subpage of the proxy.
-   */
-  openAuthenticationPage(url = config.authV2.url) {
-    if (!this.redirectToAuthPage) {
+  authCollab(config) {
+    if (this.authenticating) {
       return;
     }
-    console.info('openAuthenticationPage - origin=' + window.location.origin);
-
-    this.clearStoredToken();
+    this.authenticating = true;
+
+    return new Promise(resolve => {
+      this.authURL = this.authURL || config.url;
+
+      this.initKeycloakClient().then(() => {
+        if (!keycloakClient.authenticated) {
+          // User is not authenticated, run login
+          keycloakClient
+            .login({ scope: 'openid profile email group' })
+            .then(() => {
+              resolve(true);
+            });
+        }
+        else {
+          keycloakClient.loadUserInfo().then(userInfo => {
+            this.userInfo = userInfo;
+            resolve(true);
+          });
+        }
+      });
+    });
+  }
 
-    let absoluteUrl = /^https?:\/\//i;
-    if (!absoluteUrl.test(url)) {
-      url = `https://${url}`;
-    }
-    /*window.location.href = `${url}
-      &client_id=${this.CLIENT_ID}
-      &redirect_uri=${encodeURIComponent(window.location.href)}`;*/
-
-    let testClientID = 'community-apps-tutorial';
-    console.info('redirect_uri=' + window.location.origin);
-    let testRedirectURI = window.location.origin; //'http://localhost:3000';
-    window.location.href = url +
-      '&client_id=' + testClientID +
-      '&redirect_uri=' + testRedirectURI;
+  initKeycloakClient() {
+    return new Promise(resolve => {
+      keycloakClient = Keycloak({
+        realm: 'hbp',
+        clientId: this.clientId,
+        //'public-client': true,
+        'confidential-port': 0,
+        url: this.authURL,
+        redirectUri: window.location.href // 'http://localhost:9001/#/esv-private' //
+      });
+
+      keycloakClient
+        .init({
+          flow: 'hybrid' /*, responseMode: 'fragment'*/
+        })
+        .then(() => {
+          resolve(keycloakClient);
+        });
+    });
   }
 }
 
diff --git a/src/services/experiments/files/experiment-storage-service.js b/src/services/experiments/files/experiment-storage-service.js
index 8d63263..ac20e1a 100644
--- a/src/services/experiments/files/experiment-storage-service.js
+++ b/src/services/experiments/files/experiment-storage-service.js
@@ -253,13 +253,11 @@ class ExperimentStorageService extends HttpService {
   async setFile(directoryPath, filename, data, byname = true, contentType = 'text/plain') {
     let directory = directoryPath.replaceAll('/', '%2F');
     const url = new URL(`${config.api.proxy.url}${endpoints.proxy.storage.url}/${directory}/${filename}`);
-    //console.info(url);
     url.searchParams.append('byname', byname);
 
     let requestOptions = {
       ...this.POSTOptions, ...{ headers: { 'Content-Type': contentType } }
     };
-    //console.info(requestOptions);
 
     if (contentType === 'text/plain') {
       return this.httpRequestPOST(url, data, requestOptions);
diff --git a/src/services/http-service.js b/src/services/http-service.js
index 30651bc..87abf46 100644
--- a/src/services/http-service.js
+++ b/src/services/http-service.js
@@ -44,7 +44,9 @@ export class HttpService extends EventEmitter {
    */
   performRequest = async (url, options, data) => {
     // Add authorization header
-    options.headers.Authorization = `Bearer ${AuthenticationService.instance.getStoredToken()}`;
+    await AuthenticationService.instance.promiseInitialized;
+    let token = AuthenticationService.instance.getToken();
+    options.headers.Authorization = 'Bearer ' + token;
     if (data) {
       options.body = data;
     }
@@ -54,10 +56,7 @@ export class HttpService extends EventEmitter {
     // error handling
     if (!response.ok) {
       if (response.status === 477) {
-        const responseText = await response.text();
-        console.info('auth error');
-        console.info(responseText);
-        AuthenticationService.instance.openAuthenticationPage(/*responseText*/);
+        AuthenticationService.instance.authenticate();
       }
       else if (response.status === 478) {
         //TODO: redirect to maintenance page
diff --git a/src/services/proxy/nrp-user-service.js b/src/services/proxy/nrp-user-service.js
index 94f0024..08b6af6 100644
--- a/src/services/proxy/nrp-user-service.js
+++ b/src/services/proxy/nrp-user-service.js
@@ -62,7 +62,6 @@ class NrpUserService extends HttpService {
   async getCurrentUser() {
     if (!this.currentUser) {
       let responseIdentity = await this.httpRequestGET(IDENTITY_ME_URL);
-      console.info(responseIdentity);
       if (responseIdentity.ok) {
         this.currentUser = await responseIdentity.json();
       }
diff --git a/src/services/roslib-service.js b/src/services/roslib-service.js
index ad6c364..aa32447 100644
--- a/src/services/roslib-service.js
+++ b/src/services/roslib-service.js
@@ -1,7 +1,7 @@
 import * as ROSLIB from 'roslib';
 import _ from 'lodash';
 
-import AuthenticationService from './authentication-service.js';
+import AuthenticationService from './authentication-service-v2';
 
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
@@ -32,7 +32,7 @@ class RoslibService {
    */
   getConnection(url) {
     if (!this.connections.has(url)) {
-      let urlWithAuth = url + '?token=' + AuthenticationService.instance.getStoredToken();
+      let urlWithAuth = url + '?token=' + AuthenticationService.instance.getToken();
       this.connections.set(url, new ROSLIB.Ros({ url: urlWithAuth }));
     }
 
-- 
GitLab