From dcf243b6223af52d937f58dc54d54d05af542559 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Wed, 20 Apr 2022 20:41:00 +0200
Subject: [PATCH] Instance public private => WIP

---
 client/src/app/admin/admin.component.ts       |  2 -
 .../edit-instance-group.component.html        |  6 +-
 .../edit-instance-group.component.ts          |  4 --
 .../instance-group-list.component.html        | 36 +++++------
 .../instance-group-list.component.ts          |  4 --
 .../app/core/containers/app.component.html    |  4 +-
 .../src/app/core/containers/app.component.ts  |  7 +++
 .../app/metamodel/models/instance.model.ts    |  2 +
 .../metamodel/selectors/instance.selector.ts  |  2 +-
 .../components/instance-card.component.html   |  4 +-
 .../components/instance-card.component.ts     | 39 +++++++++++-
 .../containers/portal-home.component.html     |  5 ++
 .../containers/portal-home.component.ts       |  5 +-
 docker-compose.yml                            |  2 +-
 server/src/Action/InstanceListAction.php      | 60 +++++++++----------
 15 files changed, 110 insertions(+), 72 deletions(-)

diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts
index 4eaacb1e..f3c4d66c 100644
--- a/client/src/app/admin/admin.component.ts
+++ b/client/src/app/admin/admin.component.ts
@@ -19,7 +19,6 @@ import * as authSelector from 'src/app/auth/auth.selector';
 import * as databaseActions from 'src/app/metamodel/actions/database.actions';
 import * as selectActions from 'src/app/metamodel/actions/select.actions';
 import * as optionActions from 'src/app/metamodel/actions/select-option.actions';
-import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions';
 import { AppConfigService } from 'src/app/app-config.service';
 
 @Component({
@@ -60,7 +59,6 @@ export class AdminComponent implements OnInit {
         Promise.resolve(null).then(() => this.store.dispatch(databaseActions.loadDatabaseList()));
         Promise.resolve(null).then(() => this.store.dispatch(selectActions.loadSelectList()));
         Promise.resolve(null).then(() => this.store.dispatch(optionActions.loadSelectOptionList()));
-        Promise.resolve(null).then(() => this.store.dispatch(instanceGroupActions.loadInstanceGroupList()));
     }
 
     getBaseHref() {
diff --git a/client/src/app/admin/instance/containers/edit-instance-group.component.html b/client/src/app/admin/instance/containers/edit-instance-group.component.html
index d00e72bf..d3283c9f 100644
--- a/client/src/app/admin/instance/containers/edit-instance-group.component.html
+++ b/client/src/app/admin/instance/containers/edit-instance-group.component.html
@@ -11,15 +11,15 @@
                     Instance groups
                 </a>
             </li>
-            <li *ngIf="instanceGroupListIsLoaded | async" class="breadcrumb-item active" aria-current="page">Edit instance group {{ (instanceGroup | async).role }}</li>
+            <li class="breadcrumb-item active" aria-current="page">Edit instance group {{ (instanceGroup | async).role }}</li>
         </ol>
     </nav>
 </div>
 
 <div class="container">
-    <app-spinner *ngIf="(instanceGroupListIsLoading | async) || (instanceListIsLoading | async)"></app-spinner>
+    <app-spinner *ngIf="(instanceListIsLoading | async)"></app-spinner>
 
-    <div *ngIf="(instanceGroupListIsLoaded | async) && (instanceListIsLoaded | async)" class="row">
+    <div *ngIf="(instanceListIsLoaded | async)" class="row">
         <div class="col-12">
             <app-instance-group-form [instanceGroup]="instanceGroup | async" [instanceList]="instanceList | async" (onSubmit)="editInstanceGroup($event)" #formGroup>
                 <button [disabled]="!formGroup.form.valid || formGroup.form.pristine" type="submit" class="btn btn-primary">
diff --git a/client/src/app/admin/instance/containers/edit-instance-group.component.ts b/client/src/app/admin/instance/containers/edit-instance-group.component.ts
index 72be239d..df134b18 100644
--- a/client/src/app/admin/instance/containers/edit-instance-group.component.ts
+++ b/client/src/app/admin/instance/containers/edit-instance-group.component.ts
@@ -21,16 +21,12 @@ import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-gro
     templateUrl: 'edit-instance-group.component.html'
 })
 export class EditInstanceGroupComponent {
-    public instanceGroupListIsLoading: Observable<boolean>;
-    public instanceGroupListIsLoaded: Observable<boolean>;
     public instanceGroup: Observable<InstanceGroup>;
     public instanceListIsLoading: Observable<boolean>;
     public instanceListIsLoaded: Observable<boolean>;
     public instanceList: Observable<Instance[]>;
 
     constructor(private store: Store<{ }>) {
-        this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading);
-        this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded);
         this.instanceGroup = store.select(instanceGroupSelector.selectInstanceGroupByRouteId);
         this.instanceListIsLoading = store.select(instanceSelector.selectInstanceListIsLoading);
         this.instanceListIsLoaded = store.select(instanceSelector.selectInstanceListIsLoaded);
diff --git a/client/src/app/admin/instance/containers/instance-group-list.component.html b/client/src/app/admin/instance/containers/instance-group-list.component.html
index 0b602d11..aec46ffc 100644
--- a/client/src/app/admin/instance/containers/instance-group-list.component.html
+++ b/client/src/app/admin/instance/containers/instance-group-list.component.html
@@ -10,28 +10,24 @@
         </ol>
     </nav>
 
-    <app-spinner *ngIf="instanceGroupListIsLoading | async"></app-spinner>
-
-    <ng-container *ngIf="instanceGroupListIsLoaded | async">
-        <div class="row">
-            <div class="col-12">
-                <button title="Add a new instance group" class="btn btn-outline-success float-right" routerLink="new-group">
-                    <span class="fas fa-plus"></span> New instance group
-                </button>
-            </div>
+    <div class="row">
+        <div class="col-12">
+            <button title="Add a new instance group" class="btn btn-outline-success float-right" routerLink="new-group">
+                <span class="fas fa-plus"></span> New instance group
+            </button>
         </div>
+    </div>
 
-        <div class="row mt-1">
-            <div class="col-12 lead text-center font-weight-bold" *ngIf="(instanceGroupList | async).length < 1">
-                Oops! No instance groups available...
-            </div>
+    <div class="row mt-1">
+        <div class="col-12 lead text-center font-weight-bold" *ngIf="(instanceGroupList | async).length < 1">
+            Oops! No instance groups available...
+        </div>
 
-            <div class="col-12" *ngIf="(instanceGroupList | async).length > 0">
-                <app-instance-group-table 
-                    [instanceGroupList]="instanceGroupList | async" 
-                    (deleteInstanceGroup)="deleteInstanceGroup($event)">
-                </app-instance-group-table>
-            </div>
+        <div class="col-12" *ngIf="(instanceGroupList | async).length > 0">
+            <app-instance-group-table 
+                [instanceGroupList]="instanceGroupList | async" 
+                (deleteInstanceGroup)="deleteInstanceGroup($event)">
+            </app-instance-group-table>
         </div>
-    </ng-container>
+    </div>
 </div>
diff --git a/client/src/app/admin/instance/containers/instance-group-list.component.ts b/client/src/app/admin/instance/containers/instance-group-list.component.ts
index c874f8cd..ca05b03d 100644
--- a/client/src/app/admin/instance/containers/instance-group-list.component.ts
+++ b/client/src/app/admin/instance/containers/instance-group-list.component.ts
@@ -20,13 +20,9 @@ import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-gro
     templateUrl: 'instance-group-list.component.html'
 })
 export class InstanceGroupListComponent {
-    public instanceGroupListIsLoading: Observable<boolean>;
-    public instanceGroupListIsLoaded: Observable<boolean>;
     public instanceGroupList: Observable<InstanceGroup[]>;
 
     constructor(private store: Store<{ }>) {
-        this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading);
-        this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded);
         this.instanceGroupList = store.select(instanceGroupSelector.selectAllInstanceGroups);
     }
 
diff --git a/client/src/app/core/containers/app.component.html b/client/src/app/core/containers/app.component.html
index 9238c985..860bf595 100644
--- a/client/src/app/core/containers/app.component.html
+++ b/client/src/app/core/containers/app.component.html
@@ -1,8 +1,8 @@
-<div *ngIf="(instanceListIsLoading | async)" class="row justify-content-center mt-5">
+<div *ngIf="(instanceListIsLoading | async) || (instanceGroupListIsLoading | async)" class="row justify-content-center mt-5">
     <span class="fas fa-circle-notch fa-spin fa-3x"></span>
     <span class="sr-only">Loading...</span>
 </div>
-<router-outlet *ngIf="(instanceListIsLoaded | async)"></router-outlet>
+<router-outlet *ngIf="(instanceListIsLoaded | async) && (instanceGroupListIsLoaded | async)"></router-outlet>
 <footer class="footer mt-auto bg-light">
     <div class="container my-3">
         <div class="row justify-content-center font-weight-bold mb-1">
diff --git a/client/src/app/core/containers/app.component.ts b/client/src/app/core/containers/app.component.ts
index 8a49719a..fd2f2cd4 100644
--- a/client/src/app/core/containers/app.component.ts
+++ b/client/src/app/core/containers/app.component.ts
@@ -18,6 +18,8 @@ import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
 import * as instanceActions from 'src/app/metamodel/actions/instance.actions';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions';
+import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector';
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import { AppConfigService } from 'src/app/app-config.service';
 
@@ -34,6 +36,8 @@ export class AppComponent implements OnInit {
     public year: number = (new Date()).getFullYear();
     public instanceListIsLoading: Observable<boolean>;
     public instanceListIsLoaded: Observable<boolean>;
+    public instanceGroupListIsLoading: Observable<boolean>;
+    public instanceGroupListIsLoaded: Observable<boolean>;
     public isAuthenticated: Observable<boolean>;
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
@@ -41,6 +45,8 @@ export class AppComponent implements OnInit {
     constructor(private store: Store<{ auth: fromAuth.State }>, private config: AppConfigService) {
         this.instanceListIsLoading = store.select(instanceSelector.selectInstanceListIsLoading);
         this.instanceListIsLoaded = store.select(instanceSelector.selectInstanceListIsLoaded);
+        this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading);
+        this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded);
         this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
@@ -50,6 +56,7 @@ export class AppComponent implements OnInit {
         // Create a micro task that is processed after the current synchronous code
         // This micro task prevent the expression has changed after view init error
         Promise.resolve(null).then(() => this.store.dispatch(instanceActions.loadInstanceList()));
+        Promise.resolve(null).then(() => this.store.dispatch(instanceGroupActions.loadInstanceGroupList()));
     }
 
     /**
diff --git a/client/src/app/metamodel/models/instance.model.ts b/client/src/app/metamodel/models/instance.model.ts
index 7ac7685c..62945722 100644
--- a/client/src/app/metamodel/models/instance.model.ts
+++ b/client/src/app/metamodel/models/instance.model.ts
@@ -21,6 +21,8 @@ export interface Instance {
     wavelength_domain: string;
     display: number;
     data_path: string;
+    files_path: string;
+    public: boolean;
     portal_logo: string;
     design_color: string;
     design_background_color: string;
diff --git a/client/src/app/metamodel/selectors/instance.selector.ts b/client/src/app/metamodel/selectors/instance.selector.ts
index 907204f6..98b5c4f0 100644
--- a/client/src/app/metamodel/selectors/instance.selector.ts
+++ b/client/src/app/metamodel/selectors/instance.selector.ts
@@ -56,4 +56,4 @@ export const selectInstanceByRouteName = createSelector(
 export const selectInstanceNameByRoute = createSelector(
     reducer.selectRouterState,
     router => router.state.params.iname as string
-);
\ No newline at end of file
+);
diff --git a/client/src/app/portal/components/instance-card.component.html b/client/src/app/portal/components/instance-card.component.html
index 53869a04..9e34b92d 100644
--- a/client/src/app/portal/components/instance-card.component.html
+++ b/client/src/app/portal/components/instance-card.component.html
@@ -22,8 +22,8 @@
         </p>
     </div>
     <div class="card-footer bg-transparent text-right">
-        <a routerLink="/instance/{{ instance.name }}" class="btn btn-outline-primary" title="Go to instance">
+        <button [disabled]="!isInstanceAccessible()" routerLink="/instance/{{ instance.name }}" class="btn btn-outline-primary" title="Go to instance">
             Go to instance
-        </a>
+        </button>
     </div>
 </div>
diff --git a/client/src/app/portal/components/instance-card.component.ts b/client/src/app/portal/components/instance-card.component.ts
index 7269b4ef..d46067e5 100644
--- a/client/src/app/portal/components/instance-card.component.ts
+++ b/client/src/app/portal/components/instance-card.component.ts
@@ -9,8 +9,7 @@
 
 import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
-import { AppConfigService } from 'src/app/app-config.service';
-import { Instance } from 'src/app/metamodel/models';
+import { Instance, InstanceGroup } from 'src/app/metamodel/models';
 
 /**
  * @class
@@ -24,6 +23,11 @@ import { Instance } from 'src/app/metamodel/models';
 })
 export class InstanceCardComponent {
     @Input() instance: Instance;
+    @Input() authenticationEnabled: boolean;
+    @Input() isAuthenticated: boolean;
+    @Input() userRoles: string[];
+    @Input() adminRoles: string[];
+    @Input() instanceGroupList: InstanceGroup[];
     @Input() apiUrl: string;
 
     /**
@@ -34,4 +38,35 @@ export class InstanceCardComponent {
     getLogoSrc(): string {
         return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.portal_logo}`;
     }
+
+    isInstanceAccessible() {
+        let accessible = true;
+
+        if (this.authenticationEnabled && !this.instance.public && !this.isAdmin()) {
+            accessible = false;
+            if (this.isAuthenticated) {
+                accessible = this.instanceGroupList
+                    .filter(instanceGroup => instanceGroup.instances.includes(this.instance.name))
+                    .filter(instanceGroup => this.userRoles.includes(instanceGroup.role))
+                    .length > 0;
+            }
+        }
+
+        return accessible;
+    }
+
+    /**
+     * Returns true if user is admin
+     * 
+     * @returns boolean
+     */
+    isAdmin() {
+        let admin = false;
+        for (let i = 0; i < this.adminRoles.length; i++) {
+            admin = this.userRoles.includes(this.adminRoles[i]);
+            if (admin) break;
+        }
+
+        return admin;
+    }
 }
diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html
index 39a2bfe5..29c5b00f 100644
--- a/client/src/app/portal/containers/portal-home.component.html
+++ b/client/src/app/portal/containers/portal-home.component.html
@@ -28,6 +28,11 @@
             <div class="col-auto mb-3" *ngFor="let instance of (instanceList | async)">
                 <app-instance-card
                     [instance]="instance"
+                    [authenticationEnabled]="getAuthenticationEnabled()"
+                    [isAuthenticated]="isAuthenticated | async"
+                    [userRoles]="userRoles | async"
+                    [adminRoles]="getAdminRoles()"
+                    [instanceGroupList]="instanceGroupList | async"
                     [apiUrl]="getApiUrl()">
                 </app-instance-card>
             </div>
diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts
index 5bc6fe2a..9afedfa1 100644
--- a/client/src/app/portal/containers/portal-home.component.ts
+++ b/client/src/app/portal/containers/portal-home.component.ts
@@ -14,10 +14,11 @@ import { Store } from '@ngrx/store';
 import * as fromRouter from '@ngrx/router-store';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
-import { Instance } from 'src/app/metamodel/models';
+import { Instance, InstanceGroup } from 'src/app/metamodel/models';
 import * as authActions from 'src/app/auth/auth.actions';
 import * as authSelector from 'src/app/auth/auth.selector';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
+import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector';
 import { AppConfigService } from 'src/app/app-config.service';
 
 /**
@@ -40,6 +41,7 @@ export class PortalHomeComponent implements OnInit {
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
     public instanceList: Observable<Instance[]>;
+    public instanceGroupList: Observable<InstanceGroup[]>;
     public url: Observable<string>;
     public userRolesSubscription: Subscription;
 
@@ -48,6 +50,7 @@ export class PortalHomeComponent implements OnInit {
         this.userProfile = store.select(authSelector.selectUserProfile);
         this.userRoles = store.select(authSelector.selectUserRoles);
         this.instanceList = store.select(instanceSelector.selectAllInstances);
+        this.instanceGroupList = store.select(instanceGroupSelector.selectAllInstanceGroups);
         this.url = store.select(fromRouter.getSelectors().selectUrl);
     }
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 3172ebe3..77088f75 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -35,7 +35,7 @@ services:
             SSO_AUTH_URL: "http://localhost:8180/auth"
             SSO_REALM: "anis"
             SSO_CLIENT_ID: "anis-client"
-            TOKEN_ENABLED: 0
+            TOKEN_ENABLED: 1
             TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs"
             TOKEN_ADMIN_ROLES: anis_admin,superuser
             RMQ_HOST: rmq
diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php
index 4e2820c5..83f44202 100644
--- a/server/src/Action/InstanceListAction.php
+++ b/server/src/Action/InstanceListAction.php
@@ -63,8 +63,8 @@ final class InstanceListAction extends AbstractAction
         }
 
         if ($request->getMethod() === GET) {
-            //$instances = $this->em->getRepository('App\Entity\Instance')->findAll();
-            $instances = $this->getInstanceList($request->getAttribute('token'));
+            $instances = $this->em->getRepository('App\Entity\Instance')->findAll();
+            //$instances = $this->getInstanceList($request->getAttribute('token'));
             $payload = json_encode($instances);
         }
 
@@ -90,34 +90,34 @@ final class InstanceListAction extends AbstractAction
         return $response;
     }
 
-    private function getInstanceList($token)
-    {
-        $qb = $this->em->createQueryBuilder();
-        $qb->select('i')->from('App\Entity\Instance', 'i');
-
-        if (boolval($this->settings['enabled'])) {
-            if (!$token) {
-                // If user is not connected return public instances
-                $qb->andWhere($qb->expr()->eq('i.public', 'true'));
-            } else {
-                $adminRoles = explode(',', $this->settings['admin_roles']);
-                $roles = $token->realm_access->roles;
-                if (!$this->isAdmin($adminRoles, $roles)) {
-                    // If user is not an admin return public datasets
-                    // And returns datasets from user's groups
-                    $qb->andWhere($qb->expr()->eq('i.public', 'true'));
-                    $qb2 = $this->em->createQueryBuilder();
-                    $qb2->select('i2.name')
-                        ->from('App\Entity\InstanceGroup', 'ig')
-                        ->join('ig.instances', 'i2')
-                        ->where($qb2->expr()->in('ig.role', $roles));
-                    $qb->orWhere($qb->expr()->in('i.name', $qb2->getDQL()));
-                }
-            }
-        }
-
-        return $qb->getQuery()->getResult();
-    }
+    // private function getInstanceList($token)
+    // {
+    //     $qb = $this->em->createQueryBuilder();
+    //     $qb->select('i')->from('App\Entity\Instance', 'i');
+
+    //     if (boolval($this->settings['enabled'])) {
+    //         if (!$token) {
+    //             // If user is not connected return public instances
+    //             $qb->andWhere($qb->expr()->eq('i.public', 'true'));
+    //         } else {
+    //             $adminRoles = explode(',', $this->settings['admin_roles']);
+    //             $roles = $token->realm_access->roles;
+    //             if (!$this->isAdmin($adminRoles, $roles)) {
+    //                 // If user is not an admin return public datasets
+    //                 // And returns datasets from user's groups
+    //                 $qb->andWhere($qb->expr()->eq('i.public', 'true'));
+    //                 $qb2 = $this->em->createQueryBuilder();
+    //                 $qb2->select('i2.name')
+    //                     ->from('App\Entity\InstanceGroup', 'ig')
+    //                     ->join('ig.instances', 'i2')
+    //                     ->where($qb2->expr()->in('ig.role', $roles));
+    //                 $qb->orWhere($qb->expr()->in('i.name', $qb2->getDQL()));
+    //             }
+    //         }
+    //     }
+
+    //     return $qb->getQuery()->getResult();
+    // }
 
     /**
      * Add a new instance into the metamodel
-- 
GitLab