From bbaaaed4b5f00dfba958b1257f2362a21b381e10 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 19 Jun 2020 17:26:11 +0200 Subject: [PATCH 1/5] 71504: short-lived token for downloading files through FileService --- src/app/core/auth/auth-request.service.ts | 29 +++++++++++++++++-- src/app/core/auth/auth.service.ts | 10 +++++++ .../auth/token-response-parsing.service.ts | 23 +++++++++++++++ src/app/core/cache/response.models.ts | 14 +++++++++ src/app/core/core.module.ts | 2 ++ src/app/core/data/request.models.ts | 10 +++++++ src/app/core/shared/file.service.ts | 26 ++++++++--------- 7 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 src/app/core/auth/token-response-parsing.service.ts diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 465fb69dd2..50a285bdf9 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,12 +1,19 @@ import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; -import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; import { GlobalConfig } from '../../../config/global-config.interface'; import { isNotEmpty } from '../../shared/empty.util'; -import { AuthGetRequest, AuthPostRequest, GetRequest, PostRequest, RestRequest } from '../data/request.models'; -import { AuthStatusResponse, ErrorResponse } from '../cache/response.models'; +import { + AuthGetRequest, + AuthPostRequest, + GetRequest, + PostRequest, + RestRequest, + TokenPostRequest +} from '../data/request.models'; +import { AuthStatusResponse, ErrorResponse, RestResponse, TokenResponse } from '../cache/response.models'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { getResponseFromEntry } from '../shared/operators'; import { HttpClient } from '@angular/common/http'; @@ -15,6 +22,7 @@ import { HttpClient } from '@angular/common/http'; export class AuthRequestService { protected linkName = 'authn'; protected browseEndpoint = ''; + protected shortlivedtokensEndpoint = 'shortlivedtokens'; constructor(protected halService: HALEndpointService, protected requestService: RequestService, @@ -67,4 +75,19 @@ export class AuthRequestService { mergeMap((request: GetRequest) => this.fetchRequest(request)), distinctUntilChanged()); } + + /** + * Send a POST request to retrieve a short-lived token which provides download access of restricted files + */ + public getShortlivedToken(): Observable { + return this.halService.getEndpoint(`${this.linkName}/${this.shortlivedtokensEndpoint}`).pipe( + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => new TokenPostRequest(this.requestService.generateRequestId(), endpointURL)), + tap((request: PostRequest) => this.requestService.configure(request)), + switchMap((request: PostRequest) => this.requestService.getByUUID(request.uuid)), + getResponseFromEntry(), + map((response: TokenResponse) => response.token) + ); + } } diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 588d9e2675..dc1ad4ddd7 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -546,4 +546,14 @@ export class AuthService { return this.getImpersonateID() === epersonId; } + /** + * Get a short-lived token for appending to download urls of restricted files + * Returns null if the user isn't authenticated + */ + getShortlivedToken(): Observable { + return this.isAuthenticated().pipe( + switchMap((authenticated) => authenticated ? this.authRequestService.getShortlivedToken() : observableOf(null)) + ); + } + } diff --git a/src/app/core/auth/token-response-parsing.service.ts b/src/app/core/auth/token-response-parsing.service.ts new file mode 100644 index 0000000000..a1b1e23aa4 --- /dev/null +++ b/src/app/core/auth/token-response-parsing.service.ts @@ -0,0 +1,23 @@ +import { ResponseParsingService } from '../data/parsing.service'; +import { RestRequest } from '../data/request.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { RestResponse, TokenResponse } from '../cache/response.models'; +import { isNotEmpty } from '../../shared/empty.util'; +import { Injectable } from '@angular/core'; + +@Injectable() +/** + * A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to a token string + * wrapped in a TokenResponse + */ +export class TokenResponseParsingService implements ResponseParsingService { + + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { + if (isNotEmpty(data.payload) && isNotEmpty(data.payload.token) && (data.statusCode === 200)) { + return new TokenResponse(data.payload.token, true, data.statusCode, data.statusText); + } else { + return new TokenResponse(null, false, data.statusCode, data.statusText) + } + } + +} diff --git a/src/app/core/cache/response.models.ts b/src/app/core/cache/response.models.ts index 3f46ecf647..7439b05355 100644 --- a/src/app/core/cache/response.models.ts +++ b/src/app/core/cache/response.models.ts @@ -211,6 +211,20 @@ export class AuthStatusResponse extends RestResponse { } } +/** + * A REST Response containing a token + */ +export class TokenResponse extends RestResponse { + constructor( + public token: string, + public isSuccessful: boolean, + public statusCode: number, + public statusText: string + ) { + super(isSuccessful, statusCode, statusText); + } +} + export class IntegrationSuccessResponse extends RestResponse { constructor( public dataDefinition: PaginatedList, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 715f7a5cc0..7426cffda3 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -145,6 +145,7 @@ import { Version } from './shared/version.model'; import { VersionHistory } from './shared/version-history.model'; import { WorkflowActionDataService } from './data/workflow-action-data.service'; import { WorkflowAction } from './tasks/models/workflow-action-object.model'; +import { TokenResponseParsingService } from './auth/token-response-parsing.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -264,6 +265,7 @@ const PROVIDERS = [ LicenseDataService, ItemTypeDataService, WorkflowActionDataService, + TokenResponseParsingService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 5866cce797..8f05114b32 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -20,6 +20,7 @@ import { URLCombiner } from '../url-combiner/url-combiner'; import { TaskResponseParsingService } from '../tasks/task-response-parsing.service'; import { ContentSourceResponseParsingService } from './content-source-response-parsing.service'; import { MappedCollectionsReponseParsingService } from './mapped-collections-reponse-parsing.service'; +import { TokenResponseParsingService } from '../auth/token-response-parsing.service'; /* tslint:disable:max-classes-per-file */ @@ -241,6 +242,15 @@ export class AuthGetRequest extends GetRequest { } } +/** + * A POST request for retrieving a token + */ +export class TokenPostRequest extends PostRequest { + getResponseParser(): GenericConstructor { + return TokenResponseParsingService; + } +} + export class IntegrationRequest extends GetRequest { constructor(uuid: string, href: string) { super(uuid, href); diff --git a/src/app/core/shared/file.service.ts b/src/app/core/shared/file.service.ts index 7e89a4e5dd..841cb60869 100644 --- a/src/app/core/shared/file.service.ts +++ b/src/app/core/shared/file.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; -import { HttpHeaders } from '@angular/common/http'; - -import { DSpaceRESTv2Service, HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; -import { RestRequestMethod } from '../data/rest-request-method'; -import { saveAs } from 'file-saver'; +import { Inject, Injectable } from '@angular/core'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { AuthService } from '../auth/auth.service'; +import { take } from 'rxjs/operators'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; +import { URLCombiner } from '../url-combiner/url-combiner'; +import { hasValue } from '../../shared/empty.util'; /** * Provides utility methods to save files on the client-side. @@ -12,22 +12,20 @@ import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response. @Injectable() export class FileService { constructor( - private restService: DSpaceRESTv2Service + @Inject(NativeWindowService) protected _window: NativeWindowRef, + private authService: AuthService ) { } /** - * Makes a HTTP Get request to download a file + * Combines an URL with a short-lived token and sets the current URL to the newly created one * * @param url * file url */ downloadFile(url: string) { - const headers = new HttpHeaders(); - const options: HttpOptions = Object.create({headers, responseType: 'blob'}); - return this.restService.request(RestRequestMethod.GET, url, null, options) - .subscribe((data) => { - saveAs(data.payload as Blob, this.getFileNameFromResponseContentDisposition(data)); - }); + this.authService.getShortlivedToken().pipe(take(1)).subscribe((token) => { + this._window.nativeWindow.location.href = hasValue(token) ? new URLCombiner(url, `?token=${token}`).toString() : url; + }); } /** From b578aa408bd5737d33342f8f393d8d2330ae203b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 22 Jun 2020 11:40:39 +0200 Subject: [PATCH 2/5] 71504: FileDownloadLinkComponent --- .../full-file-section.component.html | 4 +- .../file-section/file-section.component.html | 4 +- .../file-download-link.component.html | 6 +++ .../file-download-link.component.scss | 0 .../file-download-link.component.spec.ts | 25 ++++++++++ .../file-download-link.component.ts | 48 +++++++++++++++++++ src/app/shared/shared.module.ts | 7 ++- 7 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 src/app/shared/file-download-link/file-download-link.component.html create mode 100644 src/app/shared/file-download-link/file-download-link.component.scss create mode 100644 src/app/shared/file-download-link/file-download-link.component.spec.ts create mode 100644 src/app/shared/file-download-link/file-download-link.component.ts diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html index b8ab9bdb41..7c1719eb82 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html @@ -21,9 +21,9 @@ diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.html b/src/app/+item-page/simple/field-components/file-section/file-section.component.html index 6533322e03..17e4a795e7 100644 --- a/src/app/+item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.html @@ -1,11 +1,11 @@ diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html new file mode 100644 index 0000000000..06624c8b40 --- /dev/null +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/shared/file-download-link/file-download-link.component.scss b/src/app/shared/file-download-link/file-download-link.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts new file mode 100644 index 0000000000..7e1999816b --- /dev/null +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileDownloadLinkComponent } from './file-download-link.component'; + +describe('FileDownloadLinkComponent', () => { + let component: FileDownloadLinkComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FileDownloadLinkComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FileDownloadLinkComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts new file mode 100644 index 0000000000..9df7c191ff --- /dev/null +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -0,0 +1,48 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FileService } from '../../core/shared/file.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { AuthService } from '../../core/auth/auth.service'; + +@Component({ + selector: 'ds-file-download-link', + templateUrl: './file-download-link.component.html', + styleUrls: ['./file-download-link.component.scss'] +}) +/** + * Component displaying a download link + * When the user is authenticated, a short-lived token retrieved from the REST API is added to the download link, + * ensuring the user is authorized to download the file. + */ +export class FileDownloadLinkComponent implements OnInit { + /** + * Href to link to + */ + @Input() href: string; + + /** + * Optional file name for the download + */ + @Input() download: string; + + /** + * Whether or not the current user is authenticated + */ + isAuthenticated$: Observable; + + constructor(private fileService: FileService, + private authService: AuthService) { } + + ngOnInit() { + this.isAuthenticated$ = this.authService.isAuthenticated(); + } + + /** + * Start a download of the file + * Return false to ensure the original href is displayed when the user hovers over the link + */ + downloadFile(): boolean { + this.fileService.downloadFile(this.href); + return false; + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 67d7db5c5d..09d090406a 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -202,6 +202,7 @@ import { ResourcePolicyTargetResolver } from './resource-policies/resolvers/reso import { ResourcePolicyResolver } from './resource-policies/resolvers/resource-policy.resolver'; import { EpersonSearchBoxComponent } from './resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component'; import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component'; +import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -386,7 +387,8 @@ const COMPONENTS = [ ResourcePolicyFormComponent, EpersonGroupListComponent, EpersonSearchBoxComponent, - GroupSearchBoxComponent + GroupSearchBoxComponent, + FileDownloadLinkComponent, ]; const ENTRY_COMPONENTS = [ @@ -459,7 +461,8 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsApproveComponent, ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, - ClaimedTaskActionsEditMetadataComponent + ClaimedTaskActionsEditMetadataComponent, + FileDownloadLinkComponent, ]; const SHARED_ITEM_PAGE_COMPONENTS = [ From 24858fceab35fda1429ebaa44732a40797159352 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 22 Jun 2020 13:44:16 +0200 Subject: [PATCH 3/5] 71504: Shortlived tokens + file-download-link test cases --- src/app/core/auth/auth.service.spec.ts | 57 ++++++++++++++++++- .../token-response-parsing.service.spec.ts | 45 +++++++++++++++ .../file-download-link.component.spec.ts | 40 +++++++++++-- .../testing/auth-request-service.stub.ts | 5 ++ 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 src/app/core/auth/token-response-parsing.service.spec.ts diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index 3b6fae4dd1..a15d604cc4 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -1,17 +1,14 @@ import { async, inject, TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; - import { Store, StoreModule } from '@ngrx/store'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { of as observableOf } from 'rxjs'; - import { authReducer, AuthState } from './auth.reducer'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { AuthService, IMPERSONATING_COOKIE } from './auth.service'; import { RouterStub } from '../../shared/testing/router.stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; - import { CookieService } from '../services/cookie.service'; import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service.stub'; import { AuthRequestService } from './auth-request.service'; @@ -49,6 +46,7 @@ describe('AuthService test', () => { let storage: CookieService; let token: AuthTokenInfo; let authenticatedState; + let unAuthenticatedState; let linkService; function init() { @@ -67,6 +65,13 @@ describe('AuthService test', () => { authToken: token, user: EPersonMock }; + unAuthenticatedState = { + authenticated: false, + loaded: true, + loading: false, + authToken: undefined, + user: undefined + }; authRequest = new AuthRequestServiceStub(); routeStub = new ActivatedRouteStub(); linkService = { @@ -214,6 +219,12 @@ describe('AuthService test', () => { }); }); + it('should return the shortlived token when user is logged in', () => { + authService.getShortlivedToken().subscribe((shortlivedToken: string) => { + expect(shortlivedToken).toEqual(authRequest.mockShortLivedToken); + }); + }); + it('should return token object when it is valid', () => { authService.hasValidAuthenticationToken().subscribe((tokenState: AuthTokenInfo) => { expect(tokenState).toBe(token); @@ -448,4 +459,44 @@ describe('AuthService test', () => { }); }); }); + + describe('when user is not logged in', () => { + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({ authReducer }, { + runtimeChecks: { + strictStateImmutability: false, + strictActionImmutability: false + } + }) + ], + providers: [ + { provide: AuthRequestService, useValue: authRequest }, + { provide: REQUEST, useValue: {} }, + { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: RemoteDataBuildService, useValue: linkService }, + CookieService, + AuthService + ] + }).compileComponents(); + })); + + beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => { + store + .subscribe((state) => { + (state as any).core = Object.create({}); + (state as any).core.auth = unAuthenticatedState; + }); + authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store); + })); + + it('should return null for the shortlived token', () => { + authService.getShortlivedToken().subscribe((shortlivedToken: string) => { + expect(shortlivedToken).toBeNull(); + }); + }); + }); }); diff --git a/src/app/core/auth/token-response-parsing.service.spec.ts b/src/app/core/auth/token-response-parsing.service.spec.ts new file mode 100644 index 0000000000..35927708f6 --- /dev/null +++ b/src/app/core/auth/token-response-parsing.service.spec.ts @@ -0,0 +1,45 @@ +import { TokenResponseParsingService } from './token-response-parsing.service'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { TokenResponse } from '../cache/response.models'; + +describe('TokenResponseParsingService', () => { + let service: TokenResponseParsingService; + + beforeEach(() => { + service = new TokenResponseParsingService(); + }); + + describe('parse', () => { + it('should return a TokenResponse containing the token', () => { + const data = { + payload: { + token: 'valid-token' + }, + statusCode: 200, + statusText: 'OK' + } as DSpaceRESTV2Response; + const expected = new TokenResponse(data.payload.token, true, 200, 'OK'); + expect(service.parse(undefined, data)).toEqual(expected); + }); + + it('should return an empty TokenResponse when payload doesn\'t contain a token', () => { + const data = { + payload: {}, + statusCode: 200, + statusText: 'OK' + } as DSpaceRESTV2Response; + const expected = new TokenResponse(null, false, 200, 'OK'); + expect(service.parse(undefined, data)).toEqual(expected); + }); + + it('should return an error TokenResponse when the response failed', () => { + const data = { + payload: {}, + statusCode: 400, + statusText: 'BAD REQUEST' + } as DSpaceRESTV2Response; + const expected = new TokenResponse(null, false, 400, 'BAD REQUEST'); + expect(service.parse(undefined, data)).toEqual(expected); + }); + }); +}); diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 7e1999816b..ac1751d43d 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -1,14 +1,33 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - import { FileDownloadLinkComponent } from './file-download-link.component'; +import { AuthService } from '../../core/auth/auth.service'; +import { FileService } from '../../core/shared/file.service'; +import { of as observableOf } from 'rxjs'; describe('FileDownloadLinkComponent', () => { let component: FileDownloadLinkComponent; let fixture: ComponentFixture; + let authService: AuthService; + let fileService: FileService; + let href: string; + + function init() { + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true) + }); + fileService = jasmine.createSpyObj('fileService', ['downloadFile']); + href = 'test-download-file-link'; + } + beforeEach(async(() => { + init(); TestBed.configureTestingModule({ - declarations: [ FileDownloadLinkComponent ] + declarations: [ FileDownloadLinkComponent ], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: FileService, useValue: fileService } + ] }) .compileComponents(); })); @@ -16,10 +35,23 @@ describe('FileDownloadLinkComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(FileDownloadLinkComponent); component = fixture.componentInstance; + component.href = href; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('downloadFile', () => { + let result; + + beforeEach(() => { + result = component.downloadFile(); + }); + + it('should call fileService.downloadFile with the provided href', () => { + expect(fileService.downloadFile).toHaveBeenCalledWith(href); + }); + + it('should return false', () => { + expect(result).toEqual(false); + }); }); }); diff --git a/src/app/shared/testing/auth-request-service.stub.ts b/src/app/shared/testing/auth-request-service.stub.ts index 1dc04380df..671c9237bf 100644 --- a/src/app/shared/testing/auth-request-service.stub.ts +++ b/src/app/shared/testing/auth-request-service.stub.ts @@ -9,6 +9,7 @@ import { EPersonMock } from './eperson.mock'; export class AuthRequestServiceStub { protected mockUser: EPerson = EPersonMock; protected mockTokenInfo = new AuthTokenInfo('test_token'); + protected mockShortLivedToken = 'test-shortlived-token'; public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable { const authStatusStub: AuthStatus = new AuthStatus(); @@ -82,4 +83,8 @@ export class AuthRequestServiceStub { } return obj; } + + public getShortlivedToken() { + return observableOf(this.mockShortLivedToken); + } } From 9a4f962a21b6f386162f459f2d33ba3457f3d140 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 1 Jul 2020 11:08:22 +0200 Subject: [PATCH 4/5] 71504: Remove unused imports --- src/app/core/auth/auth-request.service.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 50a285bdf9..93f55389f9 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,9 +1,8 @@ import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; -import { GlobalConfig } from '../../../config/global-config.interface'; import { isNotEmpty } from '../../shared/empty.util'; import { AuthGetRequest, @@ -13,7 +12,7 @@ import { RestRequest, TokenPostRequest } from '../data/request.models'; -import { AuthStatusResponse, ErrorResponse, RestResponse, TokenResponse } from '../cache/response.models'; +import { AuthStatusResponse, ErrorResponse, TokenResponse } from '../cache/response.models'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { getResponseFromEntry } from '../shared/operators'; import { HttpClient } from '@angular/common/http'; From 68ecc7ac315b568d6103fab73daf20bcd7941dd7 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 1 Jul 2020 18:42:24 +0200 Subject: [PATCH 5/5] rename token param to authentication-token --- src/app/core/shared/file.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/shared/file.service.ts b/src/app/core/shared/file.service.ts index 841cb60869..ca0a409b2d 100644 --- a/src/app/core/shared/file.service.ts +++ b/src/app/core/shared/file.service.ts @@ -24,7 +24,7 @@ export class FileService { */ downloadFile(url: string) { this.authService.getShortlivedToken().pipe(take(1)).subscribe((token) => { - this._window.nativeWindow.location.href = hasValue(token) ? new URLCombiner(url, `?token=${token}`).toString() : url; + this._window.nativeWindow.location.href = hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url; }); }