diff --git a/client/src/app/auth/auth.actions.ts b/client/src/app/auth/auth.actions.ts index 52da0604574a5b963966ee9e99312cf229c29d91..4103bcebd10660ab2d67a814181a2300af67872a 100644 --- a/client/src/app/auth/auth.actions.ts +++ b/client/src/app/auth/auth.actions.ts @@ -16,4 +16,5 @@ export const logout = createAction('[Auth] Logout'); export const authSuccess = createAction('[Auth] Auth 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 }>()); export const openEditProfile = createAction('[Auth] Edit Profile'); diff --git a/client/src/app/auth/auth.effects.ts b/client/src/app/auth/auth.effects.ts index 69cefb0e2b4f468564a02cdcbafb631a22113e7f..a663b0a069bce19419f982be0a4ce012ac52ff08 100644 --- a/client/src/app/auth/auth.effects.ts +++ b/client/src/app/auth/auth.effects.ts @@ -10,7 +10,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { from } from 'rxjs'; -import { tap, switchMap } from 'rxjs/operators'; +import { tap, switchMap, withLatestFrom } from 'rxjs/operators'; import { KeycloakService } from 'keycloak-angular'; @@ -49,9 +49,11 @@ export class AuthEffects { ofType(authActions.authSuccess), switchMap(() => from(this.keycloak.loadUserProfile()) .pipe( - switchMap(userProfile => [ + withLatestFrom(this.keycloak.getToken()), + switchMap(([userProfile, token]) => [ authActions.loadUserProfileSuccess({ userProfile }), - authActions.loadUserRoleSuccess({ userRoles: this.keycloak.getUserRoles() }) + authActions.loadUserRoleSuccess({ userRoles: this.keycloak.getUserRoles() }), + authActions.loadTokenSuccess({ token }) ]) ) ) diff --git a/client/src/app/auth/auth.reducer.ts b/client/src/app/auth/auth.reducer.ts index d0805affc1d7dfeb02d4b6f6c2cc2bf728c401c5..d181f446c38c60c85008746abebb2709a3d74e64 100644 --- a/client/src/app/auth/auth.reducer.ts +++ b/client/src/app/auth/auth.reducer.ts @@ -16,12 +16,14 @@ export interface State { isAuthenticated: boolean; userProfile: UserProfile; userRoles: string[]; + token: string; } export const initialState: State = { isAuthenticated: false, userProfile: null, - userRoles: [] + userRoles: [], + token: null }; export const authReducer = createReducer( @@ -37,9 +39,14 @@ export const authReducer = createReducer( on(authActions.loadUserRoleSuccess, (state, { userRoles }) => ({ ...state, userRoles + })), + on(authActions.loadTokenSuccess, (state, { token }) => ({ + ...state, + token })) ); export const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectUserProfile = (state: State) => state.userProfile; export const selectUserRoles = (state: State) => state.userRoles; +export const selectToken = (state: State) => state.token; diff --git a/client/src/app/auth/auth.selector.ts b/client/src/app/auth/auth.selector.ts index 8b0e4f734c29257f3e9004722e5aa802053d6a6d..3822a9055d1df824dc751a6ac04ecf7b6c9ea866 100644 --- a/client/src/app/auth/auth.selector.ts +++ b/client/src/app/auth/auth.selector.ts @@ -27,3 +27,8 @@ export const selectUserRoles = createSelector( selectAuthState, fromAuth.selectUserRoles ); + +export const selectToken = createSelector( + selectAuthState, + fromAuth.selectToken +); 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 f9f6b3aaa6a57531613c217cf6a1ebf3501bc5be..f854be3243fe7e38b59814ca08d52593e70da7ca 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,6 +19,7 @@ [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 a276dec48ebe3974e8494c21d4cda2602d64794b..51d18381cc91d321ea591568700f04fafcd0f0e9 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,6 +33,7 @@ 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/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index d9b4fdbf2a8260fc9d97ecb02df3294d25a04e9f..a350d6ef113a40dcaa15ac265b88c56f50a7df22 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -82,6 +82,7 @@ [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 f62ca538ed8b10b2a94be5d2b5e3778e92907a22..16a1278464bba34e97cd60d30acc9d3457784daf 100644 --- a/client/src/app/instance/search/containers/result.component.ts +++ b/client/src/app/instance/search/containers/result.component.ts @@ -20,6 +20,7 @@ import * as searchActions from '../../store/actions/search.actions'; import * as searchSelector from '../../store/selectors/search.selector'; import * as sampActions from '../../store/actions/samp.actions'; import * as sampSelector from '../../store/selectors/samp.selector'; +import * as authSelector from 'src/app/auth/auth.selector'; @Component({ selector: 'app-result', @@ -42,6 +43,7 @@ export class ResultComponent extends AbstractSearchComponent { public dataIsLoaded: Observable<boolean>; public selectedData: Observable<any>; public sampRegistered: Observable<boolean>; + public token: Observable<string>; private pristineSubscription: Subscription; @@ -56,6 +58,7 @@ 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 7b7ee23a3405f901e1183f30f59c9dbbbfeceefc..f97a2bb29e04aa12762b429657b683a4db5dce70 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 @@ -61,6 +61,8 @@ <app-download-renderer [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 1e90851ca9be53f1a232c612002eb5a8ae64981f..83c362b75644e90dbf392f7fe73b316c52aaa462 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,6 +34,7 @@ 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.ts b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts index 19bcc38572ede27f043ee814f2f330d76eabeb25..40b49a0b16daa0f7c6014b8a854d6117d0f6dca4 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 @@ -25,6 +25,8 @@ import { AppConfigService } from 'src/app/app-config.service'; export class DownloadRendererComponent { @Input() value: string; @Input() datasetName: string; + @Input() datasetPublic: boolean; + @Input() token: string; @Input() config: DownloadRendererConfig; constructor(private appConfig: AppConfigService) { } @@ -35,7 +37,11 @@ export class DownloadRendererComponent { * @return string */ getHref(): string { - return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value; + let href = getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value; + if (!this.datasetPublic && this.token) { + href += '?token=' + this.token; + } + return href; } /** diff --git a/conf-dev/public_key b/conf-dev/public_key index 7a109409de071d2cc41b153c799ebddb3f142318..6d9fd24a85f3dbb188e0bbc86e620415f641b46c 100644 --- a/conf-dev/public_key +++ b/conf-dev/public_key @@ -1,3 +1,3 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/mnnVnoX4yuiq1AXklR//PCcNOLibWzoRcyhGQQ+Rfokcxny0uBrVmFb7xNAxgqaf3hyo0789ONUf41xQF8CO0+YdLmOfQDRegohZP4mRvppCQUozroKphGAvYt90xaFJL/IttXeRKmUk6Noc/3V0oUA+1P8SbdpIq8s+L9yXfqWmW+mjlMAU31/3gVM/Q/dTmKFnkVlwprhy4OoGzHlNaFElIjFOEICL6M3ANTBLoZwDHiwvxqXuyA9IA8LeHuVpxWmaShGA0UxA99cF3edCmkc6R4d1R7rWP0XjnjYEOYDcVhJZJlRamNuYtgnr82sgr/bJahHFxAnF3GlBHs+gwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiXxjO2Z+Mnc/L4fcY7oLIvSQXrkADSUQVnAXqfcMzfuNH/clLBMmXIHoPoIOYImIbXFMNaBAFLMWqURSaz2LDEQy5dIfllU9e3kIAV0mJeafjaN3QdxKX8TcJAnrTeQ7soooeZzen7kfPWxfjAaiEtbxo5h2xW4qup+VvADQg15C3dwBS5VV2Lc41z4prQubQNs2WT4IOVfYYhtpp+R/3IuCAFS6qfmAuBUIu8gSq/VpCqJ5Sm/YdmBZDwhyBRYOKJJlkqlNFdGPnHAkBDOL5LeU54cXCBMxPEeomtu22Kv1PIF4w2kfwUie9Qq06yWsTDwyrSAjvQ3xXHAcuAqPRQIDAQAB -----END PUBLIC KEY----- diff --git a/server/src/Middleware/AuthorizationMiddleware.php b/server/src/Middleware/AuthorizationMiddleware.php index d03a3f459243bff3864772d70d858c037e5f38dc..82b5b4e040383a817331333364af1065016b4012 100644 --- a/server/src/Middleware/AuthorizationMiddleware.php +++ b/server/src/Middleware/AuthorizationMiddleware.php @@ -57,15 +57,20 @@ final class AuthorizationMiddleware implements MiddlewareInterface { if ( $request->getMethod() === OPTIONS - || !$request->hasHeader('Authorization') + || (!$request->hasHeader('Authorization') && !array_key_exists('token', $request->getQueryParams())) || !boolval($this->settings['enabled']) ) { return $handler->handle($request); } // Get token string from Authorizarion header - $bearer = $request->getHeader('Authorization'); - $data = explode(' ', $bearer[0]); + if ($request->hasHeader('Authorization')) { + $bearer = $request->getHeader('Authorization')[0]; + } else { + $bearer = 'Bearer ' . $request->getQueryParams()['token']; + } + + $data = explode(' ', $bearer); if ($data[0] !== 'Bearer') { return $this->getUnauthorizedResponse( 'HTTP 401: Authorization must contain a string with the following format -> Bearer JWT' @@ -89,7 +94,7 @@ final class AuthorizationMiddleware implements MiddlewareInterface * * @return Response */ - private function getUnauthorizedResponse(string $message): Response + private function getUnauthorizedResponse(string $message): NyholmResponse { $resonse = new NyholmResponse(); $resonse->getBody()->write(json_encode(array(