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