From 7d5e499f2190650cfd45e52c474e1f7f49252301 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 9 May 2023 17:20:54 +0200 Subject: [PATCH 1/5] make a call to ensure a correct XSRF token before performing any non-GET requests --- .../group-form/group-form.component.spec.ts | 2 + src/app/core/data/request.service.spec.ts | 9 ++- src/app/core/data/request.service.ts | 14 +++- .../core/xsrf/browser-xsrf.service.spec.ts | 64 +++++++++++++++++++ src/app/core/xsrf/browser-xsrf.service.ts | 26 ++++++++ src/app/core/xsrf/server-xsrf.service.spec.ts | 32 ++++++++++ src/app/core/xsrf/server-xsrf.service.ts | 14 ++++ src/app/core/xsrf/xsrf.service.spec.ts | 20 ++++++ src/app/core/xsrf/xsrf.service.ts | 10 +++ src/modules/app/browser-app.module.ts | 14 +++- src/modules/app/server-app.module.ts | 6 ++ 11 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/app/core/xsrf/browser-xsrf.service.spec.ts create mode 100644 src/app/core/xsrf/browser-xsrf.service.ts create mode 100644 src/app/core/xsrf/server-xsrf.service.spec.ts create mode 100644 src/app/core/xsrf/server-xsrf.service.ts create mode 100644 src/app/core/xsrf/xsrf.service.spec.ts create mode 100644 src/app/core/xsrf/xsrf.service.ts diff --git a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts index f8c5f3cd870..e281377c242 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts @@ -23,6 +23,7 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { PageInfo } from '../../../core/shared/page-info.model'; import { UUIDService } from '../../../core/shared/uuid.service'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock'; @@ -211,6 +212,7 @@ describe('GroupFormComponent', () => { { provide: HttpClient, useValue: {} }, { provide: ObjectCacheService, useValue: {} }, { provide: UUIDService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 61091c79404..c557066e165 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -1,6 +1,6 @@ import { Store, StoreModule } from '@ngrx/store'; import { cold, getTestScheduler } from 'jasmine-marbles'; -import { EMPTY, Observable, of as observableOf } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable, of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock'; @@ -8,6 +8,7 @@ import { defaultUUID, getMockUUIDService } from '../../shared/mocks/uuid.service import { ObjectCacheService } from '../cache/object-cache.service'; import { coreReducers} from '../core.reducers'; import { UUIDService } from '../shared/uuid.service'; +import { XSRFService } from '../xsrf/xsrf.service'; import { RequestConfigureAction, RequestExecuteAction, RequestStaleAction } from './request.actions'; import { DeleteRequest, @@ -35,6 +36,7 @@ describe('RequestService', () => { let uuidService: UUIDService; let store: Store; let mockStore: MockStore; + let xsrfService: XSRFService; const testUUID = '5f2a0d2a-effa-4d54-bd54-5663b960f9eb'; const testHref = 'https://rest.api/endpoint/selfLink'; @@ -80,10 +82,15 @@ describe('RequestService', () => { store = TestBed.inject(Store); mockStore = store as MockStore; mockStore.setState(initialState); + xsrfService = { + tokenInitialized$: new BehaviorSubject(false), + } as XSRFService; + service = new RequestService( objectCache, uuidService, store, + xsrfService, undefined ); serviceAsAny = service as any; diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 9f43c3f5992..0af6e4098fd 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -11,6 +11,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { IndexState, MetaIndexState } from '../index/index.reducer'; import { requestIndexSelector, getUrlWithoutEmbedParams } from '../index/index.selectors'; import { UUIDService } from '../shared/uuid.service'; +import { XSRFService } from '../xsrf/xsrf.service'; import { RequestConfigureAction, RequestExecuteAction, @@ -137,6 +138,7 @@ export class RequestService { constructor(private objectCache: ObjectCacheService, private uuidService: UUIDService, private store: Store, + protected xsrfService: XSRFService, private indexStore: Store) { } @@ -419,7 +421,17 @@ export class RequestService { */ private dispatchRequest(request: RestRequest) { this.store.dispatch(new RequestConfigureAction(request)); - this.store.dispatch(new RequestExecuteAction(request.uuid)); + // If it's a GET request, or we have an XSRF token, dispatch it immediately + if (request.method === RestRequestMethod.GET || this.xsrfService.tokenInitialized$.getValue() === true) { + this.store.dispatch(new RequestExecuteAction(request.uuid)); + } else { + // Otherwise wait for the XSRF token first + this.xsrfService.tokenInitialized$.pipe( + find((hasInitialized: boolean) => hasInitialized === true), + ).subscribe(() => { + this.store.dispatch(new RequestExecuteAction(request.uuid)); + }); + } } /** diff --git a/src/app/core/xsrf/browser-xsrf.service.spec.ts b/src/app/core/xsrf/browser-xsrf.service.spec.ts new file mode 100644 index 00000000000..378df0e46ba --- /dev/null +++ b/src/app/core/xsrf/browser-xsrf.service.spec.ts @@ -0,0 +1,64 @@ +import { BrowserXSRFService } from './browser-xsrf.service'; +import { HttpClient } from '@angular/common/http'; +import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +describe(`BrowserXSRFService`, () => { + let service: BrowserXSRFService; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; + + const endpointURL = new RESTURLCombiner('/security/csrf').toString(); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule ], + providers: [ BrowserXSRFService ] + }); + httpClient = TestBed.inject(HttpClient); + httpTestingController = TestBed.inject(HttpTestingController); + service = TestBed.inject(BrowserXSRFService); + }); + + describe(`initXSRFToken`, () => { + it(`should perform a POST to the csrf endpoint`, () => { + service.initXSRFToken(httpClient)(); + + const req = httpTestingController.expectOne({ + url: endpointURL, + method: 'POST' + }); + + req.flush({}); + httpTestingController.verify(); + }); + + describe(`when the POST succeeds`, () => { + it(`should set tokenInitialized$ to true`, () => { + service.initXSRFToken(httpClient)(); + + const req = httpTestingController.expectOne(endpointURL); + + req.flush({}); + httpTestingController.verify(); + + expect(service.tokenInitialized$.getValue()).toBeTrue(); + }); + }); + + describe(`when the POST fails`, () => { + it(`should set tokenInitialized$ to true`, () => { + service.initXSRFToken(httpClient)(); + + const req = httpTestingController.expectOne(endpointURL); + + req.error(new ErrorEvent('415')); + httpTestingController.verify(); + + expect(service.tokenInitialized$.getValue()).toBeTrue(); + }); + }); + + }); +}); diff --git a/src/app/core/xsrf/browser-xsrf.service.ts b/src/app/core/xsrf/browser-xsrf.service.ts new file mode 100644 index 00000000000..271e8dca47a --- /dev/null +++ b/src/app/core/xsrf/browser-xsrf.service.ts @@ -0,0 +1,26 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { take, catchError } from 'rxjs/operators'; +import { of as observableOf } from 'rxjs'; +import { XSRFService } from './xsrf.service'; + +@Injectable() +export class BrowserXSRFService extends XSRFService { + initXSRFToken(httpClient: HttpClient): () => Promise { + return () => new Promise((resolve) => { + httpClient.post(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe( + // errors are to be expected if the token and the cookie don't match, that's what we're + // trying to fix for future requests, so just emit any observable to end up in the + // subscribe + catchError(() => observableOf(null)), + take(1), + ).subscribe(() => { + this.tokenInitialized$.next(true); + }); + + // return immediately, the rest of the app doesn't need to wait for this to finish + resolve(); + }); + } +} diff --git a/src/app/core/xsrf/server-xsrf.service.spec.ts b/src/app/core/xsrf/server-xsrf.service.spec.ts new file mode 100644 index 00000000000..b2ace67dd0c --- /dev/null +++ b/src/app/core/xsrf/server-xsrf.service.spec.ts @@ -0,0 +1,32 @@ +import { ServerXSRFService } from './server-xsrf.service'; +import { HttpClient } from '@angular/common/http'; + +describe(`ServerXSRFService`, () => { + let service: ServerXSRFService; + let httpClient: HttpClient; + + beforeEach(() => { + httpClient = jasmine.createSpyObj(['post', 'get', 'request']); + service = new ServerXSRFService(); + }); + + describe(`initXSRFToken`, () => { + it(`shouldn't perform any requests`, (done: DoneFn) => { + service.initXSRFToken(httpClient)().then(() => { + for (const prop in httpClient) { + if (httpClient.hasOwnProperty(prop)) { + expect(httpClient[prop]).not.toHaveBeenCalled(); + } + } + done(); + }); + }); + + it(`should leave tokenInitialized$ on false`, (done: DoneFn) => { + service.initXSRFToken(httpClient)().then(() => { + expect(service.tokenInitialized$.getValue()).toBeFalse(); + done(); + }); + }); + }); +}); diff --git a/src/app/core/xsrf/server-xsrf.service.ts b/src/app/core/xsrf/server-xsrf.service.ts new file mode 100644 index 00000000000..1577893f952 --- /dev/null +++ b/src/app/core/xsrf/server-xsrf.service.ts @@ -0,0 +1,14 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { XSRFService } from './xsrf.service'; + +@Injectable() +export class ServerXSRFService extends XSRFService { + initXSRFToken(httpClient: HttpClient): () => Promise { + return () => new Promise((resolve) => { + // return immediately, and keep tokenInitialized$ false. The server side can make only GET + // requests, since it can never get a valid XSRF cookie + resolve(); + }); + } +} diff --git a/src/app/core/xsrf/xsrf.service.spec.ts b/src/app/core/xsrf/xsrf.service.spec.ts new file mode 100644 index 00000000000..a7c5c01cb7d --- /dev/null +++ b/src/app/core/xsrf/xsrf.service.spec.ts @@ -0,0 +1,20 @@ +import { XSRFService } from './xsrf.service'; +import { HttpClient } from '@angular/common/http'; + +class XSRFServiceImpl extends XSRFService { + initXSRFToken(httpClient: HttpClient): () => Promise { + return () => null; + } +} + +describe(`XSRFService`, () => { + let service: XSRFService; + + beforeEach(() => { + service = new XSRFServiceImpl(); + }); + + it(`should start with tokenInitialized$.hasValue() === false`, () => { + expect(service.tokenInitialized$.getValue()).toBeFalse(); + }); +}); diff --git a/src/app/core/xsrf/xsrf.service.ts b/src/app/core/xsrf/xsrf.service.ts new file mode 100644 index 00000000000..fb8dfe74b33 --- /dev/null +++ b/src/app/core/xsrf/xsrf.service.ts @@ -0,0 +1,10 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable() +export abstract class XSRFService { + public tokenInitialized$: BehaviorSubject = new BehaviorSubject(false); + + abstract initXSRFToken(httpClient: HttpClient): () => Promise; +} diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index d6295cf791d..2f1f6a200ea 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -1,5 +1,5 @@ import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule, BrowserTransferStateModule, makeStateKey, TransferState } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { REQUEST } from '@nguniversal/express-engine/tokens'; @@ -32,6 +32,8 @@ import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; import { BrowserInitService } from './browser-init.service'; import { ReferrerService } from '../../app/core/services/referrer.service'; +import { BrowserXSRFService } from '../../app/core/xsrf/browser-xsrf.service'; +import { XSRFService } from '../../app/core/xsrf/xsrf.service'; import { BrowserReferrerService } from '../../app/core/services/browser.referrer.service'; export const REQ_KEY = makeStateKey('req'); @@ -73,6 +75,16 @@ export function getRequest(transferState: TransferState): any { useFactory: getRequest, deps: [TransferState] }, + { + provide: APP_INITIALIZER, + useFactory: (xsrfService: XSRFService, httpClient: HttpClient) => xsrfService.initXSRFToken(httpClient), + deps: [ XSRFService, HttpClient ], + multi: true, + }, + { + provide: XSRFService, + useClass: BrowserXSRFService, + }, { provide: AuthService, useClass: AuthService diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index b3d718b0b2b..970a852be4b 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -35,6 +35,8 @@ import { ServerAuthRequestService } from '../../app/core/auth/server-auth-reques import { ServerInitService } from './server-init.service'; import { XhrFactory } from '@angular/common'; import { ServerXhrService } from '../../app/core/services/server-xhr.service'; +import { ServerXSRFService } from '../../app/core/xsrf/server-xsrf.service'; +import { XSRFService } from '../../app/core/xsrf/xsrf.service'; import { ReferrerService } from '../../app/core/services/referrer.service'; import { ServerReferrerService } from '../../app/core/services/server.referrer.service'; @@ -94,6 +96,10 @@ export function createTranslateLoader(transferState: TransferState) { provide: AuthRequestService, useClass: ServerAuthRequestService, }, + { + provide: XSRFService, + useClass: ServerXSRFService, + }, { provide: LocaleService, useClass: ServerLocaleService From 769210bef2cda11a9e3103730a5e71fbcf145a95 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 Apr 2024 10:25:43 -0500 Subject: [PATCH 2/5] Update code to use GET request. Cleanup lint errors & add in basic TypeDocs --- .../core/xsrf/browser-xsrf.service.spec.ts | 36 ++++++++----------- src/app/core/xsrf/browser-xsrf.service.ts | 20 ++++++----- src/app/core/xsrf/server-xsrf.service.spec.ts | 3 +- src/app/core/xsrf/server-xsrf.service.ts | 7 +++- src/app/core/xsrf/xsrf.service.spec.ts | 3 +- src/app/core/xsrf/xsrf.service.ts | 5 +++ 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/app/core/xsrf/browser-xsrf.service.spec.ts b/src/app/core/xsrf/browser-xsrf.service.spec.ts index 378df0e46ba..aba3edd3305 100644 --- a/src/app/core/xsrf/browser-xsrf.service.spec.ts +++ b/src/app/core/xsrf/browser-xsrf.service.spec.ts @@ -1,8 +1,12 @@ -import { BrowserXSRFService } from './browser-xsrf.service'; import { HttpClient } from '@angular/common/http'; -import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { BrowserXSRFService } from './browser-xsrf.service'; describe(`BrowserXSRFService`, () => { let service: BrowserXSRFService; @@ -14,7 +18,7 @@ describe(`BrowserXSRFService`, () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ], - providers: [ BrowserXSRFService ] + providers: [ BrowserXSRFService ], }); httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); @@ -22,20 +26,22 @@ describe(`BrowserXSRFService`, () => { }); describe(`initXSRFToken`, () => { - it(`should perform a POST to the csrf endpoint`, () => { + it(`should perform a GET to the csrf endpoint`, (done: DoneFn) => { service.initXSRFToken(httpClient)(); const req = httpTestingController.expectOne({ url: endpointURL, - method: 'POST' + method: 'GET', }); req.flush({}); httpTestingController.verify(); + expect().nothing(); + done(); }); - describe(`when the POST succeeds`, () => { - it(`should set tokenInitialized$ to true`, () => { + describe(`when the GET succeeds`, () => { + it(`should set tokenInitialized$ to true`, (done: DoneFn) => { service.initXSRFToken(httpClient)(); const req = httpTestingController.expectOne(endpointURL); @@ -44,19 +50,7 @@ describe(`BrowserXSRFService`, () => { httpTestingController.verify(); expect(service.tokenInitialized$.getValue()).toBeTrue(); - }); - }); - - describe(`when the POST fails`, () => { - it(`should set tokenInitialized$ to true`, () => { - service.initXSRFToken(httpClient)(); - - const req = httpTestingController.expectOne(endpointURL); - - req.error(new ErrorEvent('415')); - httpTestingController.verify(); - - expect(service.tokenInitialized$.getValue()).toBeTrue(); + done(); }); }); diff --git a/src/app/core/xsrf/browser-xsrf.service.ts b/src/app/core/xsrf/browser-xsrf.service.ts index 271e8dca47a..121defc061b 100644 --- a/src/app/core/xsrf/browser-xsrf.service.ts +++ b/src/app/core/xsrf/browser-xsrf.service.ts @@ -1,21 +1,25 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { take } from 'rxjs/operators'; + import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; -import { take, catchError } from 'rxjs/operators'; -import { of as observableOf } from 'rxjs'; import { XSRFService } from './xsrf.service'; +/** + * Browser (CSR) Service to obtain a new CSRF/XSRF token when needed by our RequestService + * to perform a modify request (e.g. POST/PUT/DELETE). + * NOTE: This is primarily necessary before the *first* modifying request, as the CSRF + * token may not yet be initialized. + */ @Injectable() export class BrowserXSRFService extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { - return () => new Promise((resolve) => { - httpClient.post(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe( - // errors are to be expected if the token and the cookie don't match, that's what we're - // trying to fix for future requests, so just emit any observable to end up in the - // subscribe - catchError(() => observableOf(null)), + return () => new Promise((resolve) => { + // Force a new token to be created by calling the CSRF endpoint + httpClient.get(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe( take(1), ).subscribe(() => { + // Once token is returned, set tokenInitialized to true. this.tokenInitialized$.next(true); }); diff --git a/src/app/core/xsrf/server-xsrf.service.spec.ts b/src/app/core/xsrf/server-xsrf.service.spec.ts index b2ace67dd0c..05728edb423 100644 --- a/src/app/core/xsrf/server-xsrf.service.spec.ts +++ b/src/app/core/xsrf/server-xsrf.service.spec.ts @@ -1,6 +1,7 @@ -import { ServerXSRFService } from './server-xsrf.service'; import { HttpClient } from '@angular/common/http'; +import { ServerXSRFService } from './server-xsrf.service'; + describe(`ServerXSRFService`, () => { let service: ServerXSRFService; let httpClient: HttpClient; diff --git a/src/app/core/xsrf/server-xsrf.service.ts b/src/app/core/xsrf/server-xsrf.service.ts index 1577893f952..f729aa49a7d 100644 --- a/src/app/core/xsrf/server-xsrf.service.ts +++ b/src/app/core/xsrf/server-xsrf.service.ts @@ -1,11 +1,16 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; + import { XSRFService } from './xsrf.service'; +/** + * Server (SSR) Service to obtain a new CSRF/XSRF token. Because SSR only triggers GET + * requests a CSRF token is never needed. + */ @Injectable() export class ServerXSRFService extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { - return () => new Promise((resolve) => { + return () => new Promise((resolve) => { // return immediately, and keep tokenInitialized$ false. The server side can make only GET // requests, since it can never get a valid XSRF cookie resolve(); diff --git a/src/app/core/xsrf/xsrf.service.spec.ts b/src/app/core/xsrf/xsrf.service.spec.ts index a7c5c01cb7d..56564a294ca 100644 --- a/src/app/core/xsrf/xsrf.service.spec.ts +++ b/src/app/core/xsrf/xsrf.service.spec.ts @@ -1,6 +1,7 @@ -import { XSRFService } from './xsrf.service'; import { HttpClient } from '@angular/common/http'; +import { XSRFService } from './xsrf.service'; + class XSRFServiceImpl extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { return () => null; diff --git a/src/app/core/xsrf/xsrf.service.ts b/src/app/core/xsrf/xsrf.service.ts index fb8dfe74b33..99b27021b66 100644 --- a/src/app/core/xsrf/xsrf.service.ts +++ b/src/app/core/xsrf/xsrf.service.ts @@ -2,6 +2,11 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; +/** + * Abstract CSRF/XSRF Service used to track whether a CSRF token has been received + * from the DSpace REST API. Once it is received, the "tokenInitialized$" flag will + * be set to "true". + */ @Injectable() export abstract class XSRFService { public tokenInitialized$: BehaviorSubject = new BehaviorSubject(false); From 950038e6dfda0d2149e0c0559a5d6dc7b2289535 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 Apr 2024 11:48:13 -0500 Subject: [PATCH 3/5] Fix tests. Add XSRFService to all specs which need it to be initialized --- .../bitstream-formats/bitstream-formats.component.spec.ts | 4 +++- ...earch-result-admin-workflow-list-element.component.spec.ts | 4 +++- ...on-search-result-list-submission-element.component.spec.ts | 2 ++ .../edit-relationship-list.component.spec.ts | 2 ++ .../file-section/file-section.component.spec.ts | 2 ++ src/app/login-page/login-page.component.spec.ts | 4 +++- src/app/shared/auth-nav-menu/auth-nav-menu.component.spec.ts | 2 ++ .../auth-nav-menu/user-menu/user-menu.component.spec.ts | 4 +++- .../relation-group/dynamic-relation-group.component.spec.ts | 4 +++- .../dynamic-lookup-relation-modal.component.spec.ts | 2 ++ src/app/shared/form/form.component.spec.ts | 4 +++- .../listable-object-component-loader.component.spec.ts | 2 ++ .../item-detail-preview/item-detail-preview.component.spec.ts | 2 ++ .../collection-search-result-grid-element.component.spec.ts | 2 ++ .../community-search-result-grid-element.component.spec.ts | 4 +++- .../item-types/item/item-list-element.component.spec.ts | 2 ++ src/app/shared/search/search.component.spec.ts | 2 ++ src/app/submission/edit/submission-edit.component.spec.ts | 3 ++- .../sections/accesses/section-accesses.component.spec.ts | 3 +++ .../sections/license/section-license.component.spec.ts | 2 ++ .../file/edit/section-upload-file-edit.component.spec.ts | 2 ++ 21 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index 8a44240b7e2..0cfdf3bd297 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -15,6 +15,7 @@ import { NotificationsService } from '../../../shared/notifications/notification import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { @@ -108,7 +109,8 @@ describe('BitstreamFormatsComponent', () => { { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: NotificationsService, useValue: notificationsServiceStub }, - { provide: PaginationService, useValue: paginationService } + { provide: PaginationService, useValue: paginationService }, + { provide: XSRFService, useValue: {} }, ] }).compileComponents(); }; diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts index ab5d8b79a84..8c5258e9c60 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts @@ -9,6 +9,7 @@ import { CollectionElementLinkType } from '../../../../../shared/object-collecti import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { RouterTestingModule } from '@angular/router/testing'; import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; +import { XSRFService } from '../../../../../core/xsrf/xsrf.service'; import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './workflow-item-search-result-admin-workflow-list-element.component'; @@ -58,7 +59,8 @@ describe('WorkflowItemSearchResultAdminWorkflowListElementComponent', () => { { provide: TruncatableService, useValue: mockTruncatableService }, { provide: LinkService, useValue: linkService }, { provide: DSONameService, useClass: DSONameServiceMock }, - { provide: APP_CONFIG, useValue: environment } + { provide: APP_CONFIG, useValue: environment }, + { provide: XSRFService, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.spec.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.spec.ts index 9e4d3471506..ba11253922f 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.spec.ts @@ -20,6 +20,7 @@ import { Bitstream } from '../../../../../core/shared/bitstream.model'; import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service'; import { Item } from '../../../../../core/shared/item.model'; import { UUIDService } from '../../../../../core/shared/uuid.service'; +import { XSRFService } from '../../../../../core/xsrf/xsrf.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; @@ -115,6 +116,7 @@ describe('PersonSearchResultListElementSubmissionComponent', () => { { provide: Store, useValue: {}}, { provide: ObjectCacheService, useValue: {} }, { provide: UUIDService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, { provide: CommunityDataService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index 3a627232a42..1cdd0e0fdbc 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -28,6 +28,7 @@ import { ConfigurationDataService } from '../../../../core/data/configuration-da import { LinkHeadService } from '../../../../core/services/link-head.service'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub'; +import { XSRFService } from '../../../../core/xsrf/xsrf.service'; import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model'; import { Router } from '@angular/router'; import { RouterMock } from '../../../../shared/mocks/router.mock'; @@ -230,6 +231,7 @@ describe('EditRelationshipListComponent', () => { { provide: ConfigurationDataService, useValue: configurationDataService }, { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() }, { provide: EditItemRelationshipsService, useValue: editItemRelationshipsService }, + { provide: XSRFService, useValue: {} }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], schemas: [ NO_ERRORS_SCHEMA diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts index 8acf405b55f..02579b7900e 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts @@ -13,6 +13,7 @@ import { of as observableOf } from 'rxjs'; import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock'; import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { XSRFService } from '../../../../core/xsrf/xsrf.service'; import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; @@ -66,6 +67,7 @@ describe('FileSectionComponent', () => { }), BrowserAnimationsModule], declarations: [FileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent], providers: [ + { provide: XSRFService, useValue: {} }, { provide: BitstreamDataService, useValue: bitstreamDataService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: APP_CONFIG, useValue: environment } diff --git a/src/app/login-page/login-page.component.spec.ts b/src/app/login-page/login-page.component.spec.ts index baaa3430ac9..7f5f3a70806 100644 --- a/src/app/login-page/login-page.component.spec.ts +++ b/src/app/login-page/login-page.component.spec.ts @@ -6,6 +6,7 @@ import { Store } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; +import { XSRFService } from '../core/xsrf/xsrf.service'; import { LoginPageComponent } from './login-page.component'; import { ActivatedRouteStub } from '../shared/testing/active-router.stub'; @@ -31,7 +32,8 @@ describe('LoginPageComponent', () => { declarations: [LoginPageComponent], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, - { provide: Store, useValue: store } + { provide: Store, useValue: store }, + { provide: XSRFService, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.spec.ts b/src/app/shared/auth-nav-menu/auth-nav-menu.component.spec.ts index e3d3ef7fc5d..6abb111e5cd 100644 --- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.spec.ts +++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.spec.ts @@ -14,6 +14,7 @@ import { HostWindowService } from '../host-window.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthService } from '../../core/auth/auth.service'; +import { XSRFService } from '../../core/xsrf/xsrf.service'; import { of } from 'rxjs'; import { BrowserOnlyMockPipe } from '../testing/browser-only-mock.pipe'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @@ -86,6 +87,7 @@ describe('AuthNavMenuComponent', () => { providers: [ { provide: HostWindowService, useValue: window }, { provide: AuthService, useValue: authService }, + { provide: XSRFService, useValue: {} }, ], schemas: [ CUSTOM_ELEMENTS_SCHEMA diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.spec.ts b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.spec.ts index 04270f97cd9..3ef4847168a 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.spec.ts +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.spec.ts @@ -7,6 +7,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { UserMenuComponent } from './user-menu.component'; import { authReducer, AuthState } from '../../../core/auth/auth.reducer'; import { AuthTokenInfo } from '../../../core/auth/models/auth-token-info.model'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; import { EPersonMock } from '../../testing/eperson.mock'; import { AppState } from '../../../app.reducer'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; @@ -69,7 +70,8 @@ describe('UserMenuComponent', () => { }) ], providers: [ - { provide: AuthService, useValue: authService } + { provide: AuthService, useValue: authService }, + { provide: XSRFService, useValue: {} }, ], declarations: [ UserMenuComponent diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts index feca7f95c61..39cdd497029 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts @@ -25,6 +25,7 @@ import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service import { StoreMock } from '../../../../../testing/store.mock'; import { FormRowModel } from '../../../../../../core/config/models/config-submission-form.model'; import { storeModuleConfig } from '../../../../../../app.reducer'; +import { XSRFService } from '../../../../../../core/xsrf/xsrf.service'; export let FORM_GROUP_TEST_MODEL_CONFIG; @@ -129,7 +130,8 @@ describe('DsDynamicRelationGroupComponent test suite', () => { FormComponent, FormService, { provide: VocabularyService, useValue: new VocabularyServiceStub() }, - { provide: Store, useClass: StoreMock } + { provide: Store, useClass: StoreMock }, + { provide: XSRFService, useValue: {} }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts index 9d57296f826..3bce3073596 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts @@ -24,6 +24,7 @@ import { RemoteDataBuildService } from '../../../../../core/cache/builders/remot import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { Collection } from '../../../../../core/shared/collection.model'; import { By } from '@angular/platform-browser'; +import { XSRFService } from '../../../../../core/xsrf/xsrf.service'; describe('DsDynamicLookupRelationModalComponent', () => { let component: DsDynamicLookupRelationModalComponent; @@ -128,6 +129,7 @@ describe('DsDynamicLookupRelationModalComponent', () => { } } }, + { provide: XSRFService, useValue: {} }, { provide: NgZone, useValue: new NgZone({}) }, NgbActiveModal ], diff --git a/src/app/shared/form/form.component.spec.ts b/src/app/shared/form/form.component.spec.ts index 2f3be3fded3..5369f669f9a 100644 --- a/src/app/shared/form/form.component.spec.ts +++ b/src/app/shared/form/form.component.spec.ts @@ -24,6 +24,7 @@ import { FormFieldMetadataValueObject } from './builder/models/form-field-metada import { createTestComponent } from '../testing/utils.test'; import { BehaviorSubject } from 'rxjs'; import { storeModuleConfig } from '../../app.reducer'; +import { XSRFService } from '../../core/xsrf/xsrf.service'; let TEST_FORM_MODEL; @@ -156,7 +157,8 @@ describe('FormComponent test suite', () => { FormBuilderService, FormComponent, FormService, - { provide: Store, useClass: StoreMock } + { provide: Store, useClass: StoreMock }, + { provide: XSRFService, useValue: {} }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index e3d2ad64b12..dcaa061bad3 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -26,6 +26,7 @@ import { FileService } from '../../../../core/shared/file.service'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { ChangeDetectionStrategy } from '@angular/core'; import { SearchResultListElementComponent } from '../../../object-list/search-result-list-element/search-result-list-element.component'; +import { XSRFService } from 'src/app/core/xsrf/xsrf.service'; const testType = 'TestType'; const testContext = Context.Search; @@ -72,6 +73,7 @@ describe('ListableObjectComponentLoaderComponent', () => { { provide: FileService, useValue: fileService }, { provide: ThemeService, useValue: themeService }, { provide: TruncatableService, useValue: truncatableService }, + { provide: XSRFService, useValue: {} }, ] }).overrideComponent(ListableObjectComponentLoaderComponent, { set: { diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts index 0b5acd343bd..99facd98298 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts @@ -20,6 +20,7 @@ import { FileService } from '../../../../core/shared/file.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { Item } from '../../../../core/shared/item.model'; import { UUIDService } from '../../../../core/shared/uuid.service'; +import { XSRFService } from '../../../../core/xsrf/xsrf.service'; import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; import { NotificationsService } from '../../../notifications/notifications.service'; import { HALEndpointServiceStub } from '../../../testing/hal-endpoint-service.stub'; @@ -99,6 +100,7 @@ describe('ItemDetailPreviewComponent', () => { { provide: HALEndpointService, useValue: new HALEndpointServiceStub('workspaceitems') }, { provide: ObjectCacheService, useValue: {} }, { provide: UUIDService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, { provide: CommunityDataService, useValue: {} }, diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts index 15548d66082..0be0d12e220 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts @@ -13,6 +13,7 @@ import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.ser import { Collection } from '../../../../core/shared/collection.model'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { UUIDService } from '../../../../core/shared/uuid.service'; +import { XSRFService } from '../../../../core/xsrf/xsrf.service'; import { NotificationsService } from '../../../notifications/notifications.service'; import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; @@ -80,6 +81,7 @@ describe('CollectionSearchResultGridElementComponent', () => { { provide: DSOChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, { provide: BitstreamFormatDataService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: LinkService, useValue: linkService } ], diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts index 710fbf3f672..f98bb36d178 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts @@ -13,6 +13,7 @@ import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.ser import { Community } from '../../../../core/shared/community.model'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { UUIDService } from '../../../../core/shared/uuid.service'; +import { XSRFService } from '../../../../core/xsrf/xsrf.service'; import { NotificationsService } from '../../../notifications/notifications.service'; import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; @@ -80,7 +81,8 @@ describe('CommunitySearchResultGridElementComponent', () => { { provide: DSOChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, { provide: BitstreamFormatDataService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: XSRFService, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.spec.ts b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.spec.ts index a222c49871c..60231c5d273 100644 --- a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.spec.ts +++ b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.spec.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ItemListElementComponent } from './item-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; +import { XSRFService } from '../../../../../core/xsrf/xsrf.service'; import { TruncatableService } from '../../../../truncatable/truncatable.service'; import { of as observableOf } from 'rxjs'; import { ListableObjectComponentLoaderComponent } from '../../../../object-collection/shared/listable-object/listable-object-component-loader.component'; @@ -91,6 +92,7 @@ describe('ItemListElementComponent', () => { { provide: ActivatedRoute, useValue: activatedRoute }, { provide: AuthService, useValue: authService }, { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: XSRFService, useValue: {} }, { provide: FileService, useValue: fileService }, { provide: ThemeService, useValue: themeService }, { provide: TruncatableService, useValue: truncatableService }, diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index 69f4dfd6aaa..4eb055d80e9 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -20,6 +20,7 @@ import { SidebarService } from '../sidebar/sidebar.service'; import { SearchFilterService } from '../../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component'; +import { XSRFService } from '../../core/xsrf/xsrf.service'; import { RouteService } from '../../core/services/route.service'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { PaginatedSearchOptions } from './models/paginated-search-options.model'; @@ -208,6 +209,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar provide: SearchFilterService, useValue: {} }, + { provide: XSRFService, useValue: {} }, { provide: SEARCH_CONFIG_SERVICE, useValue: searchConfigurationServiceStub diff --git a/src/app/submission/edit/submission-edit.component.spec.ts b/src/app/submission/edit/submission-edit.component.spec.ts index 8013162d85c..a476c9ab1f9 100644 --- a/src/app/submission/edit/submission-edit.component.spec.ts +++ b/src/app/submission/edit/submission-edit.component.spec.ts @@ -18,6 +18,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { mockSubmissionObject } from '../../shared/mocks/submission.mock'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { ItemDataService } from '../../core/data/item-data.service'; +import { XSRFService } from '../../core/xsrf/xsrf.service'; import { SubmissionJsonPatchOperationsServiceStub } from '../../shared/testing/submission-json-patch-operations-service.stub'; import { SubmissionJsonPatchOperationsService } from '../../core/submission/submission-json-patch-operations.service'; @@ -54,7 +55,7 @@ describe('SubmissionEditComponent Component', () => { { provide: TranslateService, useValue: getMockTranslateService() }, { provide: Router, useValue: new RouterStub() }, { provide: ActivatedRoute, useValue: route }, - + { provide: XSRFService, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/submission/sections/accesses/section-accesses.component.spec.ts b/src/app/submission/sections/accesses/section-accesses.component.spec.ts index c70758a4df4..651223c04b9 100644 --- a/src/app/submission/sections/accesses/section-accesses.component.spec.ts +++ b/src/app/submission/sections/accesses/section-accesses.component.spec.ts @@ -40,6 +40,7 @@ import { AppState } from '../../../app.reducer'; import { getMockFormService } from '../../../shared/mocks/form-service.mock'; import { mockAccessesFormData } from '../../../shared/mocks/submission.mock'; import { accessConditionChangeEvent, checkboxChangeEvent } from '../../../shared/testing/form-event.stub'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; describe('SubmissionSectionAccessesComponent', () => { let component: SubmissionSectionAccessesComponent; @@ -96,6 +97,7 @@ describe('SubmissionSectionAccessesComponent', () => { { provide: TranslateService, useValue: getMockTranslateService() }, { provide: FormService, useValue: getMockFormService() }, { provide: Store, useValue: storeStub }, + { provide: XSRFService, useValue: {} }, { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, { provide: 'sectionDataProvider', useValue: sectionData }, { provide: 'submissionIdProvider', useValue: '1508' }, @@ -188,6 +190,7 @@ describe('SubmissionSectionAccessesComponent', () => { { provide: TranslateService, useValue: getMockTranslateService() }, { provide: FormService, useValue: getMockFormService() }, { provide: Store, useValue: storeStub }, + { provide: XSRFService, useValue: {} }, { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, { provide: 'sectionDataProvider', useValue: sectionData }, { provide: 'submissionIdProvider', useValue: '1508' }, diff --git a/src/app/submission/sections/license/section-license.component.spec.ts b/src/app/submission/sections/license/section-license.component.spec.ts index 35fea95b8df..f15dcf99643 100644 --- a/src/app/submission/sections/license/section-license.component.spec.ts +++ b/src/app/submission/sections/license/section-license.component.spec.ts @@ -38,6 +38,7 @@ import { Collection } from '../../../core/shared/collection.model'; import { License } from '../../../core/shared/license.model'; import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; import { cold } from 'jasmine-marbles'; +import { XSRFService } from '../../../core/xsrf/xsrf.service'; const collectionId = mockSubmissionCollectionId; const licenseText = 'License text'; @@ -137,6 +138,7 @@ describe('SubmissionSectionLicenseComponent test suite', () => { { provide: 'collectionIdProvider', useValue: collectionId }, { provide: 'sectionDataProvider', useValue: Object.assign({}, sectionObject) }, { provide: 'submissionIdProvider', useValue: submissionId }, + { provide: XSRFService, useValue: {} }, ChangeDetectorRef, FormBuilderService, SubmissionSectionLicenseComponent diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index 72c555f39ca..251f9aea81a 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -47,6 +47,7 @@ import { } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { dateToISOFormat } from '../../../../../shared/date.util'; import { of } from 'rxjs'; +import { XSRFService } from '../../../../../core/xsrf/xsrf.service'; const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { add: jasmine.createSpy('add'), @@ -103,6 +104,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { { provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsServiceStub }, { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder }, { provide: SectionUploadService, useValue: getMockSectionUploadService() }, + { provide: XSRFService, useValue: {} }, FormBuilderService, ChangeDetectorRef, SubmissionSectionUploadFileEditComponent, From df8f0db784da575e2fa40772852d988b5cf02872 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 Apr 2024 14:00:42 -0500 Subject: [PATCH 4/5] Update build script to use "docker compose" instead of "docker-compose". Fixes random failures starting e2e test backend via Docker --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd655a39328..50b260b59ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: #CHROME_VERSION: "90.0.4430.212-1" # Bump Node heap size (OOM in CI after upgrading to Angular 15) NODE_OPTIONS: '--max-old-space-size=4096' - # Project name to use when running docker-compose prior to e2e tests + # Project name to use when running "docker compose" prior to e2e tests COMPOSE_PROJECT_NAME: 'ci' strategy: # Create a matrix of Node versions to test against (in parallel) @@ -108,12 +108,12 @@ jobs: path: 'coverage/dspace-angular/lcov.info' retention-days: 14 - # Using docker-compose start backend using CI configuration + # Using "docker compose" start backend using CI configuration # and load assetstore from a cached copy - name: Start DSpace REST Backend via Docker (for e2e tests) run: | - docker-compose -f ./docker/docker-compose-ci.yml up -d - docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli + docker compose -f ./docker/docker-compose-ci.yml up -d + docker compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli docker container ls # Run integration tests via Cypress.io @@ -182,7 +182,7 @@ jobs: run: kill -9 $(lsof -t -i:4000) - name: Shutdown Docker containers - run: docker-compose -f ./docker/docker-compose-ci.yml down + run: docker compose -f ./docker/docker-compose-ci.yml down # Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test # job above. This is necessary because Codecov uploads seem to randomly fail at times. From 2d81a487d3a44a5d5ddaeff2067470969dfed569 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 5 Apr 2024 09:43:47 +0200 Subject: [PATCH 5/5] [DURACOM-247] Move check for initialized token to request effects --- src/app/core/data/request.effects.ts | 13 ++++++++++--- src/app/core/data/request.service.spec.ts | 9 +-------- src/app/core/data/request.service.ts | 19 +++---------------- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 889d909bfa3..d79dd9a2835 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -1,7 +1,7 @@ import { Injectable, Injector } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { catchError, filter, map, mergeMap, take } from 'rxjs/operators'; +import { catchError, filter, map, mergeMap, take, withLatestFrom } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { StoreActionTypes } from '../../store.actions'; @@ -9,6 +9,7 @@ import { getClassForType } from '../cache/builders/build-decorators'; import { RawRestResponse } from '../dspace-rest/raw-rest-response.model'; import { DspaceRestService } from '../dspace-rest/dspace-rest.service'; import { DSpaceSerializer } from '../dspace-rest/dspace.serializer'; +import { XSRFService } from '../xsrf/xsrf.service'; import { RequestActionTypes, RequestErrorAction, @@ -19,6 +20,7 @@ import { import { RequestService } from './request.service'; import { ParsedResponse } from '../cache/response.models'; import { RequestError } from './request-error.model'; +import { RestRequestMethod } from './rest-request-method'; import { RestRequestWithResponseParser } from './rest-request-with-response-parser.model'; import { RequestEntry } from './request-entry.model'; @@ -33,7 +35,11 @@ export class RequestEffects { ); }), filter((entry: RequestEntry) => hasValue(entry)), - map((entry: RequestEntry) => entry.request), + withLatestFrom(this.xsrfService.tokenInitialized$), + // If it's a GET request, or we have an XSRF token, dispatch it immediately + // Otherwise wait for the XSRF token first + filter(([entry, tokenInitialized]: [RequestEntry, boolean]) => entry.request.method === RestRequestMethod.GET || tokenInitialized === true), + map(([entry, tokenInitialized]: [RequestEntry, boolean]) => entry.request), mergeMap((request: RestRequestWithResponseParser) => { let body = request.body; if (isNotEmpty(request.body) && !request.isMultipart) { @@ -73,7 +79,8 @@ export class RequestEffects { private actions$: Actions, private restApi: DspaceRestService, private injector: Injector, - protected requestService: RequestService + protected requestService: RequestService, + protected xsrfService: XSRFService, ) { } } diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index c557066e165..ae07a1c3d5d 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -1,6 +1,6 @@ import { Store, StoreModule } from '@ngrx/store'; import { cold, getTestScheduler } from 'jasmine-marbles'; -import { BehaviorSubject, EMPTY, Observable, of as observableOf } from 'rxjs'; +import { EMPTY, Observable, of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock'; @@ -8,7 +8,6 @@ import { defaultUUID, getMockUUIDService } from '../../shared/mocks/uuid.service import { ObjectCacheService } from '../cache/object-cache.service'; import { coreReducers} from '../core.reducers'; import { UUIDService } from '../shared/uuid.service'; -import { XSRFService } from '../xsrf/xsrf.service'; import { RequestConfigureAction, RequestExecuteAction, RequestStaleAction } from './request.actions'; import { DeleteRequest, @@ -36,7 +35,6 @@ describe('RequestService', () => { let uuidService: UUIDService; let store: Store; let mockStore: MockStore; - let xsrfService: XSRFService; const testUUID = '5f2a0d2a-effa-4d54-bd54-5663b960f9eb'; const testHref = 'https://rest.api/endpoint/selfLink'; @@ -82,16 +80,11 @@ describe('RequestService', () => { store = TestBed.inject(Store); mockStore = store as MockStore; mockStore.setState(initialState); - xsrfService = { - tokenInitialized$: new BehaviorSubject(false), - } as XSRFService; service = new RequestService( objectCache, uuidService, store, - xsrfService, - undefined ); serviceAsAny = service as any; }); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 0af6e4098fd..063b19f8019 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -8,10 +8,9 @@ import cloneDeep from 'lodash/cloneDeep'; import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../../shared/empty.util'; import { ObjectCacheEntry } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { IndexState, MetaIndexState } from '../index/index.reducer'; +import { IndexState } from '../index/index.reducer'; import { requestIndexSelector, getUrlWithoutEmbedParams } from '../index/index.selectors'; import { UUIDService } from '../shared/uuid.service'; -import { XSRFService } from '../xsrf/xsrf.service'; import { RequestConfigureAction, RequestExecuteAction, @@ -137,9 +136,7 @@ export class RequestService { constructor(private objectCache: ObjectCacheService, private uuidService: UUIDService, - private store: Store, - protected xsrfService: XSRFService, - private indexStore: Store) { + private store: Store) { } generateRequestId(): string { @@ -421,17 +418,7 @@ export class RequestService { */ private dispatchRequest(request: RestRequest) { this.store.dispatch(new RequestConfigureAction(request)); - // If it's a GET request, or we have an XSRF token, dispatch it immediately - if (request.method === RestRequestMethod.GET || this.xsrfService.tokenInitialized$.getValue() === true) { - this.store.dispatch(new RequestExecuteAction(request.uuid)); - } else { - // Otherwise wait for the XSRF token first - this.xsrfService.tokenInitialized$.pipe( - find((hasInitialized: boolean) => hasInitialized === true), - ).subscribe(() => { - this.store.dispatch(new RequestExecuteAction(request.uuid)); - }); - } + this.store.dispatch(new RequestExecuteAction(request.uuid)); } /**