diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 18b97c8e9e..4a119036bc 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,5 @@ import { APP_BASE_HREF, CommonModule } from '@angular/common'; -import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @@ -38,6 +38,7 @@ import { ForbiddenComponent } from './forbidden/forbidden.component'; import { AuthInterceptor } from './core/auth/auth.interceptor'; import { LocaleInterceptor } from './core/locale/locale.interceptor'; import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor'; +import { LogInterceptor } from './core/log/log.interceptor'; import { RootComponent } from './root/root.component'; import { ThemedRootComponent } from './root/themed-root.component'; import { ThemedEntryComponentModule } from '../themes/themed-entry-component.module'; @@ -49,6 +50,9 @@ import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.com import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; +import { UUIDService } from './core/shared/uuid.service'; +import { CookieService } from './core/services/cookie.service'; + export function getBase() { return environment.ui.nameSpace; } @@ -121,6 +125,27 @@ const PROVIDERS = [ useClass: XsrfInterceptor, multi: true }, + // register LogInterceptor as HttpInterceptor + { + provide: HTTP_INTERCEPTORS, + useClass: LogInterceptor, + multi: true + }, + // insert the unique id of the user that is using the application utilizing cookies + { + provide: APP_INITIALIZER, + useFactory: (cookieService: CookieService, uuidService: UUIDService) => { + const correlationId = cookieService.get('CORRELATION-ID'); + + // Check if cookie exists, if don't, set it with unique id + if (!correlationId) { + cookieService.set('CORRELATION-ID', uuidService.generate()); + } + return () => true; + }, + multi: true, + deps: [ CookieService, UUIDService ] + }, ...DYNAMIC_MATCHER_PROVIDERS, ]; diff --git a/src/app/core/log/log.interceptor.spec.ts b/src/app/core/log/log.interceptor.spec.ts new file mode 100644 index 0000000000..9bda4b7934 --- /dev/null +++ b/src/app/core/log/log.interceptor.spec.ts @@ -0,0 +1,76 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { Router } from '@angular/router'; + +import { LogInterceptor } from './log.interceptor'; +import { DspaceRestService } from '../dspace-rest/dspace-rest.service'; +import { RestRequestMethod } from '../data/rest-request-method'; +import { CookieService } from '../services/cookie.service'; +import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock'; +import { RouterStub } from '../../shared/testing/router.stub'; + + +describe('LogInterceptor', () => { + let service: DspaceRestService; + let httpMock: HttpTestingController; + let cookieService: CookieService; + const router = Object.assign(new RouterStub(),{url : '/statistics'}); + + // Mock payload/statuses are dummy content as we are not testing the results + // of any below requests. We are only testing for X-XSRF-TOKEN header. + const mockPayload = { + id: 1 + }; + const mockStatusCode = 200; + const mockStatusText = 'SUCCESS'; + + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + DspaceRestService, + // LogInterceptor, + { + provide: HTTP_INTERCEPTORS, + useClass: LogInterceptor, + multi: true, + }, + { provide: CookieService, useValue: new CookieServiceMock() }, + { provide: Router, useValue: router }, + ], + }); + + service = TestBed.get(DspaceRestService); + httpMock = TestBed.get(HttpTestingController); + cookieService = TestBed.get(CookieService); + + cookieService.set('CORRELATION-ID','123455'); + }); + + + it('headers should be set', (done) => { + service.request(RestRequestMethod.POST, 'server/api/core/items', 'test', { withCredentials: false }).subscribe((response) => { + expect(response).toBeTruthy(); + done(); + }); + + const httpRequest = httpMock.expectOne('server/api/core/items'); + httpRequest.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText }); + expect(httpRequest.request.headers.has('X-CORRELATION-ID')).toBeTrue(); + expect(httpRequest.request.headers.has('X-REFERRER')).toBeTrue(); + }); + + it('headers should have the right values', (done) => { + service.request(RestRequestMethod.POST, 'server/api/core/items', 'test', { withCredentials: false }).subscribe((response) => { + expect(response).toBeTruthy(); + done(); + }); + + const httpRequest = httpMock.expectOne('server/api/core/items'); + httpRequest.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText }); + expect(httpRequest.request.headers.get('X-CORRELATION-ID')).toEqual('123455'); + expect(httpRequest.request.headers.get('X-REFERRER')).toEqual('/statistics'); + }); +}); diff --git a/src/app/core/log/log.interceptor.ts b/src/app/core/log/log.interceptor.ts new file mode 100644 index 0000000000..773a60b6ef --- /dev/null +++ b/src/app/core/log/log.interceptor.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Router } from '@angular/router'; + +import { Observable } from 'rxjs'; + +import { CookieService } from '../services/cookie.service'; + +/** + * Log Interceptor intercepting Http Requests & Responses to + * exchange add headers of the user using the application utilizing unique id in cookies. + * Add header for users current page path. + */ +@Injectable() +export class LogInterceptor implements HttpInterceptor { + + constructor(private cookieService: CookieService, private router: Router) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + + // Get Unique id of the user from the cookies + const correlationId = this.cookieService.get('CORRELATION-ID'); + + // Add headers from the intercepted request + let headers = request.headers; + headers = headers.append('X-CORRELATION-ID', correlationId); + headers = headers.append('X-REFERRER', this.router.url); + + // Add new headers to the intercepted request + request = request.clone({ withCredentials: true, headers: headers }); + return next.handle(request); + } +}