diff --git a/client/src/app/auth/auth.actions.ts b/client/src/app/auth/auth.actions.ts index 4103bcebd10660ab2d67a814181a2300af67872a..e661950bc51ec035d596e7ffa4c36e4b32a96d04 100644 --- a/client/src/app/auth/auth.actions.ts +++ b/client/src/app/auth/auth.actions.ts @@ -14,6 +14,7 @@ import { UserProfile } from './user-profile.model'; export const login = createAction('[Auth] Login'); export const logout = createAction('[Auth] Logout'); export const authSuccess = createAction('[Auth] Auth Success'); +export const authRefreshSuccess = createAction('[Auth] Auth Refresh Success'); export const loadUserProfileSuccess = createAction('[Auth] Load User Profile Success', props<{ userProfile: UserProfile }>()); export const loadUserRoleSuccess = createAction('[Auth] Load User Roles Success', props<{ userRoles: string[] }>()); export const loadTokenSuccess = createAction('[Auth] Load Token Success', props<{ token: string }>()); diff --git a/client/src/app/auth/auth.effects.ts b/client/src/app/auth/auth.effects.ts index a663b0a069bce19419f982be0a4ce012ac52ff08..d063947da7551c674cc504b300992724de67da7f 100644 --- a/client/src/app/auth/auth.effects.ts +++ b/client/src/app/auth/auth.effects.ts @@ -60,6 +60,19 @@ export class AuthEffects { ) ); + authRefreshSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(authActions.authRefreshSuccess), + switchMap(() => from(this.keycloak.getToken()) + .pipe( + switchMap(token => [ + authActions.loadTokenSuccess({ token }) + ]) + ) + ) + ) + ); + openEditProfile$ = createEffect(() => this.actions$.pipe( ofType(authActions.openEditProfile), diff --git a/client/src/app/auth/init.keycloak.ts b/client/src/app/auth/init.keycloak.ts index a15e68f1179781fb93e1a25d10e3c9a35e3e8ed8..d488a979c2829b954546d7bdd3ff2e9d974961f1 100644 --- a/client/src/app/auth/init.keycloak.ts +++ b/client/src/app/auth/init.keycloak.ts @@ -24,6 +24,9 @@ export function initializeKeycloak(keycloak: KeycloakService, store: Store<{ }>, if (event.type === KeycloakEventType.OnAuthSuccess) { store.dispatch(keycloakActions.authSuccess()); } + if (event.type === KeycloakEventType.OnAuthRefreshSuccess) { + store.dispatch(keycloakActions.authRefreshSuccess()); + } if (event.type === KeycloakEventType.OnAuthRefreshError) { store.dispatch(keycloakActions.login()); } diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.html b/client/src/app/instance/search/components/result/datatable-tab.component.html index f854be3243fe7e38b59814ca08d52593e70da7ca..f9f6b3aaa6a57531613c217cf6a1ebf3501bc5be 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.html +++ b/client/src/app/instance/search/components/result/datatable-tab.component.html @@ -19,7 +19,6 @@ [dataIsLoading]="dataIsLoading" [dataIsLoaded]="dataIsLoaded" [selectedData]="selectedData" - [token]="token" (retrieveData)="retrieveData.emit($event)" (addSelectedData)="addSelectedData.emit($event)" (deleteSelectedData)="deleteSelectedData.emit($event)"> diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts index 51d18381cc91d321ea591568700f04fafcd0f0e9..a276dec48ebe3974e8494c21d4cda2602d64794b 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.ts +++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts @@ -33,7 +33,6 @@ export class DatatableTabComponent { @Input() dataIsLoading: boolean; @Input() dataIsLoaded: boolean; @Input() selectedData: any[]; - @Input() token: string; @Output() retrieveData: EventEmitter<Pagination> = new EventEmitter(); @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter(); diff --git a/client/src/app/instance/search/components/result/download.component.html b/client/src/app/instance/search/components/result/download.component.html index 4f9d94496d025ae285d6bd9a7c30ad0e1db42fe8..bca9d41fafe78bb124dfef68a6541f3d811666f4 100644 --- a/client/src/app/instance/search/components/result/download.component.html +++ b/client/src/app/instance/search/components/result/download.component.html @@ -18,15 +18,15 @@ <p>Download results just here:</p> </div> <div class="col"> - <a *ngIf="getConfigDownloadResultFormat('download_csv')" [href]="getUrl('csv')" class="btn btn-outline-primary" title="Download results in CSV format"> + <a *ngIf="getConfigDownloadResultFormat('download_csv')" (click)="click($event, getUrl('csv'), 'csv')" class="btn btn-outline-primary" title="Download results in CSV format"> <i class="fas fa-file-csv"></i> CSV </a> - <a *ngIf="getConfigDownloadResultFormat('download_ascii')" [href]="getUrl('ascii')" target="_blank" class="btn btn-outline-primary" title="Download results in ASCII format"> + <a *ngIf="getConfigDownloadResultFormat('download_ascii')" (click)="click($event, getUrl('ascii'), 'txt')" class="btn btn-outline-primary" title="Download results in ASCII format"> <i class="fas fa-file"></i> ASCII </a> - <a *ngIf="getConfigDownloadResultFormat('download_vo')" [href]="getUrl('votable')" target="_blank" class="btn btn-outline-primary" title="Download results in VO format"> + <a *ngIf="getConfigDownloadResultFormat('download_vo')" (click)="click($event, getUrl('votable'), 'xml')" class="btn btn-outline-primary" title="Download results in VO format"> <i class="fas fa-file"></i> VOtable </a> @@ -41,7 +41,7 @@ <p>Download archive files just here:</p> </div> <div class="col"> - <a [href]="getUrlArchive()" class="btn btn-outline-primary" title="Download an archive with all files"> + <a (click)="click($event, getUrlArchive(), 'zip')" class="btn btn-outline-primary" title="Download an archive with all files"> <i class="fas fa-archive"></i> Files archive </a> </div> diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts index 10607d8459b2b922a870567a800cd031fd3d4578..b0498b150edd0e4fb3b223875692fd6063aac658 100644 --- a/client/src/app/instance/search/components/result/download.component.ts +++ b/client/src/app/instance/search/components/result/download.component.ts @@ -8,6 +8,7 @@ */ import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { Criterion, ConeSearch, criterionToString } from '../../../store/models'; import { Dataset } from 'src/app/metamodel/models'; @@ -34,7 +35,7 @@ export class DownloadComponent { @Input() sampRegistered: boolean; @Output() broadcast: EventEmitter<string> = new EventEmitter(); - constructor(private appConfig: AppConfigService) { } + constructor(private appConfig: AppConfigService, private http: HttpClient) { } isDownloadActivated(): boolean { const dataset = this.datasetList.find(dataset => dataset.name === this.datasetSelected); @@ -91,4 +92,17 @@ export class DownloadComponent { broadcastVotable(): void { this.broadcast.emit(this.getUrl('votable')); } + + click(event, href, extension) { + event.preventDefault(); + + this.http.get(href, {responseType: "blob"}).subscribe( + data => { + let downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL(data); + downloadLink.setAttribute('download', `${this.datasetSelected}.${extension}`); + downloadLink.click(); + } + ); + } } diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index e57e2aa6ea83613a6fb4737817d6f3ae629f705e..052cf4ed64375604dfdd2b3e57adeb0509ff5205 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -82,7 +82,6 @@ [dataIsLoading]="dataIsLoading | async" [dataIsLoaded]="dataIsLoaded | async" [selectedData]="selectedData | async" - [token]="token | async" (retrieveData)="retrieveData($event)" (addSelectedData)="addSearchData($event)" (deleteSelectedData)="deleteSearchData($event)"> diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts index 16a1278464bba34e97cd60d30acc9d3457784daf..9254082a3f03e5b3da3a26c1bd75a1062d1af818 100644 --- a/client/src/app/instance/search/containers/result.component.ts +++ b/client/src/app/instance/search/containers/result.component.ts @@ -43,7 +43,6 @@ export class ResultComponent extends AbstractSearchComponent { public dataIsLoaded: Observable<boolean>; public selectedData: Observable<any>; public sampRegistered: Observable<boolean>; - public token: Observable<string>; private pristineSubscription: Subscription; @@ -58,7 +57,6 @@ export class ResultComponent extends AbstractSearchComponent { this.dataIsLoaded = this.store.select(searchSelector.selectDataIsLoaded); this.selectedData = this.store.select(searchSelector.selectSelectedData); this.sampRegistered = this.store.select(sampSelector.selectRegistered); - this.token = this.store.select(authSelector.selectToken); } ngOnInit() { diff --git a/client/src/app/instance/shared-search/components/datatable/datatable.component.html b/client/src/app/instance/shared-search/components/datatable/datatable.component.html index c2509821e3052c7aad63f360ee18a7350fbfb1ff..ecb7f5726a7055b7592d8fc099ca53845d04bb19 100644 --- a/client/src/app/instance/shared-search/components/datatable/datatable.component.html +++ b/client/src/app/instance/shared-search/components/datatable/datatable.component.html @@ -62,7 +62,6 @@ [value]="datum[attribute.label]" [datasetName]="dataset.name" [datasetPublic]="dataset.public" - [token]="token" [config]="getRendererConfig(attribute)"> </app-download-renderer> </div> diff --git a/client/src/app/instance/shared-search/components/datatable/datatable.component.ts b/client/src/app/instance/shared-search/components/datatable/datatable.component.ts index 91b185a51efc3a7407999a5d22a06bd6d6a884a5..08038733980a47f874e9665701321a37fb373204 100644 --- a/client/src/app/instance/shared-search/components/datatable/datatable.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/datatable.component.ts @@ -34,7 +34,6 @@ export class DatatableComponent implements OnInit { @Input() dataIsLoading: boolean; @Input() dataIsLoaded: boolean; @Input() selectedData: any[] = []; - @Input() token: string; @Output() retrieveData: EventEmitter<Pagination> = new EventEmitter(); @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter(); diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.html b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.html index aef5f0ea9282018d805f76a7cd788e1921333b61..da86483575a96e40ec8973a9697a489c60ca7ae6 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.html +++ b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.html @@ -1,4 +1,4 @@ -<a [href]="getHref()" [ngClass]="{'btn btn-outline-primary btn-sm': (config.display=='text-button' || config.display=='icon-button' || config.display=='icon-text-btn')}"> +<a (click)="click($event)" [ngClass]="{'btn btn-outline-primary btn-sm': (config.display=='text-button' || config.display=='icon-button' || config.display=='icon-text-btn')}"> <span *ngIf="config.display === 'icon-button' || config.display === 'icon-text-btn'" class="{{config.icon}}"></span> <span *ngIf="config.display === 'icon-text-btn'"> </span> <span *ngIf="config.display !== 'icon-button'">{{ getText() }}</span> diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts index 40b49a0b16daa0f7c6014b8a854d6117d0f6dca4..56475b8e0d2615e254096a12f551caaa5338b4b9 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts @@ -8,6 +8,7 @@ */ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { DownloadRendererConfig } from 'src/app/metamodel/models/renderers/download-renderer-config.model'; import { getHost } from 'src/app/shared/utils'; @@ -26,10 +27,9 @@ export class DownloadRendererComponent { @Input() value: string; @Input() datasetName: string; @Input() datasetPublic: boolean; - @Input() token: string; @Input() config: DownloadRendererConfig; - constructor(private appConfig: AppConfigService) { } + constructor(private appConfig: AppConfigService, private http: HttpClient) { } /** * Returns link href. @@ -37,11 +37,7 @@ export class DownloadRendererComponent { * @return string */ getHref(): string { - let href = getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value; - if (!this.datasetPublic && this.token) { - href += '?token=' + this.token; - } - return href; + return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value; } /** @@ -52,4 +48,20 @@ export class DownloadRendererComponent { getText(): string { return this.config.text.replace('$value', this.value.toString()); } + + click(event) { + event.preventDefault(); + + const href = this.getHref(); + this.http.get(href, {responseType: "blob"}).subscribe( + data => { + const filename = href.substring(href.lastIndexOf('/') + 1); + + let downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL(data); + downloadLink.setAttribute('download', filename); + downloadLink.click(); + } + ); + } } diff --git a/conf-dev/public_key b/conf-dev/public_key index 6d9fd24a85f3dbb188e0bbc86e620415f641b46c..3c3ba0e8be160eac8c137f66f4bfb969137c8c04 100644 --- a/conf-dev/public_key +++ b/conf-dev/public_key @@ -1,3 +1,3 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiXxjO2Z+Mnc/L4fcY7oLIvSQXrkADSUQVnAXqfcMzfuNH/clLBMmXIHoPoIOYImIbXFMNaBAFLMWqURSaz2LDEQy5dIfllU9e3kIAV0mJeafjaN3QdxKX8TcJAnrTeQ7soooeZzen7kfPWxfjAaiEtbxo5h2xW4qup+VvADQg15C3dwBS5VV2Lc41z4prQubQNs2WT4IOVfYYhtpp+R/3IuCAFS6qfmAuBUIu8gSq/VpCqJ5Sm/YdmBZDwhyBRYOKJJlkqlNFdGPnHAkBDOL5LeU54cXCBMxPEeomtu22Kv1PIF4w2kfwUie9Qq06yWsTDwyrSAjvQ3xXHAcuAqPRQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1fucBQK34tl8Gx/fefATnpWqW5PVlwMYcJAqgWvmtwNm9ZW/S5HNZZfjW1S9BOJLfudCM83WHrAwGixgHKI310YXg+6BI9qn2Gnge1GC3JtKZx6UdcxZFAYmlhY0QGL5QxGR58DkEj6l/FDrwAHyVkC5sLqDMiYsqO7CA1uJLtF8yUrCyHvI4TR+kk5ZSM94/osg6eGxGSYA89u+qhG5tz5YCFgiRUNxuAEucsz8XiEfNVAz5kdYgsR4+LtmqECfczpwcJrAu7yDxs3rQPjBqFdGqEehALHX9vq71VEYe5Id2P7ddik3byHa0a21Q0RuVhMYwGrLiMLJCXqxE1YMuwIDAQAB -----END PUBLIC KEY----- diff --git a/docker-compose.yml b/docker-compose.yml index ee412b76725a54d72cfad75ec8636d000a5842f9..482db6739fd0955000d4d0dc26d19b1500fbcb39 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: SSO_AUTH_URL: "http://localhost:8180/auth" SSO_REALM: "anis" SSO_CLIENT_ID: "anis-client" - TOKEN_ENABLED: 0 + TOKEN_ENABLED: 1 TOKEN_PUBLIC_KEY_FILE: /mnt/public_key TOKEN_ADMIN_ROLE: anis_admin ports: