55
66import { AuthTokenInfo , TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model' ;
77import { DSPACE_XSRF_COOKIE , XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants' ;
8-
9- // NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
10- // from the Angular UI's config.json. See 'login()'.
11- export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server' ;
12- export const FALLBACK_TEST_REST_DOMAIN = 'localhost' ;
8+ import { v4 as uuidv4 } from 'uuid' ;
139
1410// Declare Cypress namespace to help with Intellisense & code completion in IDEs
1511// ALL custom commands MUST be listed here for code completion to work
@@ -41,6 +37,13 @@ declare global {
4137 * @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
4238 */
4339 generateViewEvent ( uuid : string , dsoType : string ) : typeof generateViewEvent ;
40+
41+ /**
42+ * Create a new CSRF token and add to required Cookie. CSRF Token is returned
43+ * in chainable in order to allow it to be sent also in required CSRF header.
44+ * @returns Chainable reference to allow CSRF token to also be sent in header.
45+ */
46+ createCSRFCookie ( ) : Chainable < any > ;
4447 }
4548 }
4649}
@@ -54,59 +57,32 @@ declare global {
5457 * @param password password to login as
5558 */
5659function login ( email : string , password : string ) : void {
57- // Cypress doesn't have access to the running application in Node.js.
58- // So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
59- // Instead, we'll read our running application's config.json, which contains the configs &
60- // is regenerated at runtime each time the Angular UI application starts up.
61- cy . task ( 'readUIConfig' ) . then ( ( str : string ) => {
62- // Parse config into a JSON object
63- const config = JSON . parse ( str ) ;
64-
65- // Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
66- let baseRestUrl = FALLBACK_TEST_REST_BASE_URL ;
67- if ( ! config . rest . baseUrl ) {
68- console . warn ( "Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL ) ;
69- } else {
70- //console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl));
71- baseRestUrl = config . rest . baseUrl ;
72- }
73-
74- // Now find domain of our REST API, again with a fallback.
75- let baseDomain = FALLBACK_TEST_REST_DOMAIN ;
76- if ( ! config . rest . host ) {
77- console . warn ( "Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN ) ;
78- } else {
79- baseDomain = config . rest . host ;
80- }
81-
82- // Create a fake CSRF Token. Set it in the required server-side cookie
83- const csrfToken = 'fakeLoginCSRFToken' ;
84- cy . setCookie ( DSPACE_XSRF_COOKIE , csrfToken , { 'domain' : baseDomain } ) ;
85-
86- // Now, send login POST request including that CSRF token
87- cy . request ( {
88- method : 'POST' ,
89- url : baseRestUrl + '/api/authn/login' ,
90- headers : { [ XSRF_REQUEST_HEADER ] : csrfToken } ,
91- form : true , // indicates the body should be form urlencoded
92- body : { user : email , password : password }
93- } ) . then ( ( resp ) => {
94- // We expect a successful login
95- expect ( resp . status ) . to . eq ( 200 ) ;
96- // We expect to have a valid authorization header returned (with our auth token)
97- expect ( resp . headers ) . to . have . property ( 'authorization' ) ;
98-
99- // Initialize our AuthTokenInfo object from the authorization header.
100- const authheader = resp . headers . authorization as string ;
101- const authinfo : AuthTokenInfo = new AuthTokenInfo ( authheader ) ;
102-
103- // Save our AuthTokenInfo object to our dsAuthInfo UI cookie
104- // This ensures the UI will recognize we are logged in on next "visit()"
105- cy . setCookie ( TOKENITEM , JSON . stringify ( authinfo ) ) ;
60+ // Create a fake CSRF cookie/token to use in POST
61+ cy . createCSRFCookie ( ) . then ( ( csrfToken : string ) => {
62+ // get our REST API's base URL, also needed for POST
63+ cy . task ( 'getRestBaseURL' ) . then ( ( baseRestUrl : string ) => {
64+ // Now, send login POST request including that CSRF token
65+ cy . request ( {
66+ method : 'POST' ,
67+ url : baseRestUrl + '/api/authn/login' ,
68+ headers : { [ XSRF_REQUEST_HEADER ] : csrfToken } ,
69+ form : true , // indicates the body should be form urlencoded
70+ body : { user : email , password : password }
71+ } ) . then ( ( resp ) => {
72+ // We expect a successful login
73+ expect ( resp . status ) . to . eq ( 200 ) ;
74+ // We expect to have a valid authorization header returned (with our auth token)
75+ expect ( resp . headers ) . to . have . property ( 'authorization' ) ;
76+
77+ // Initialize our AuthTokenInfo object from the authorization header.
78+ const authheader = resp . headers . authorization as string ;
79+ const authinfo : AuthTokenInfo = new AuthTokenInfo ( authheader ) ;
80+
81+ // Save our AuthTokenInfo object to our dsAuthInfo UI cookie
82+ // This ensures the UI will recognize we are logged in on next "visit()"
83+ cy . setCookie ( TOKENITEM , JSON . stringify ( authinfo ) ) ;
84+ } ) ;
10685 } ) ;
107-
108- // Remove cookie with fake CSRF token, as it's no longer needed
109- cy . clearCookie ( DSPACE_XSRF_COOKIE ) ;
11086 } ) ;
11187}
11288// Add as a Cypress command (i.e. assign to 'cy.login')
@@ -141,56 +117,53 @@ Cypress.Commands.add('loginViaForm', loginViaForm);
141117 * @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
142118 */
143119function generateViewEvent ( uuid : string , dsoType : string ) : void {
144- // Cypress doesn't have access to the running application in Node.js.
145- // So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
146- // Instead, we'll read our running application's config.json, which contains the configs &
147- // is regenerated at runtime each time the Angular UI application starts up.
148- cy . task ( 'readUIConfig' ) . then ( ( str : string ) => {
149- // Parse config into a JSON object
150- const config = JSON . parse ( str ) ;
120+ // Create a fake CSRF cookie/token to use in POST
121+ cy . createCSRFCookie ( ) . then ( ( csrfToken : string ) => {
122+ // get our REST API's base URL, also needed for POST
123+ cy . task ( 'getRestBaseURL' ) . then ( ( baseRestUrl : string ) => {
124+ // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
125+ cy . request ( {
126+ method : 'POST' ,
127+ url : baseRestUrl + '/api/statistics/viewevents' ,
128+ headers : {
129+ [ XSRF_REQUEST_HEADER ] : csrfToken ,
130+ // use a known public IP address to avoid being seen as a "bot"
131+ 'X-Forwarded-For' : '1.1.1.1' ,
132+ // Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot"
133+ 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0' ,
134+ } ,
135+ //form: true, // indicates the body should be form urlencoded
136+ body : { targetId : uuid , targetType : dsoType } ,
137+ } ) . then ( ( resp ) => {
138+ // We expect a 201 (which means statistics event was created)
139+ expect ( resp . status ) . to . eq ( 201 ) ;
140+ } ) ;
141+ } ) ;
142+ } ) ;
143+ }
144+ // Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
145+ Cypress . Commands . add ( 'generateViewEvent' , generateViewEvent ) ;
151146
152- // Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
153- let baseRestUrl = FALLBACK_TEST_REST_BASE_URL ;
154- if ( ! config . rest . baseUrl ) {
155- console . warn ( "Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL ) ;
156- } else {
157- baseRestUrl = config . rest . baseUrl ;
158- }
159147
160- // Now find domain of our REST API, again with a fallback.
161- let baseDomain = FALLBACK_TEST_REST_DOMAIN ;
162- if ( ! config . rest . host ) {
163- console . warn ( "Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN ) ;
164- } else {
165- baseDomain = config . rest . host ;
166- }
148+ /**
149+ * Can be used by tests to generate a random XSRF/CSRF token and save it to
150+ * the required XSRF/CSRF cookie for usage when sending POST requests or similar.
151+ * The generated CSRF token is returned in a Chainable to allow it to be also sent
152+ * in the CSRF HTTP Header.
153+ * @returns a Cypress Chainable which can be used to get the generated CSRF Token
154+ */
155+ function createCSRFCookie ( ) : Cypress . Chainable {
156+ // Generate a new token which is a random UUID
157+ const csrfToken : string = uuidv4 ( ) ;
167158
159+ // Save it to our required cookie
160+ cy . task ( 'getRestBaseDomain' ) . then ( ( baseDomain : string ) => {
168161 // Create a fake CSRF Token. Set it in the required server-side cookie
169- const csrfToken = 'fakeGenerateViewEventCSRFToken' ;
170162 cy . setCookie ( DSPACE_XSRF_COOKIE , csrfToken , { 'domain' : baseDomain } ) ;
171-
172- // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
173- cy . request ( {
174- method : 'POST' ,
175- url : baseRestUrl + '/api/statistics/viewevents' ,
176- headers : {
177- [ XSRF_REQUEST_HEADER ] : csrfToken ,
178- // use a known public IP address to avoid being seen as a "bot"
179- 'X-Forwarded-For' : '1.1.1.1' ,
180- // Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot"
181- 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0' ,
182- } ,
183- //form: true, // indicates the body should be form urlencoded
184- body : { targetId : uuid , targetType : dsoType } ,
185- } ) . then ( ( resp ) => {
186- // We expect a 201 (which means statistics event was created)
187- expect ( resp . status ) . to . eq ( 201 ) ;
188- } ) ;
189-
190- // Remove cookie with fake CSRF token, as it's no longer needed
191- cy . clearCookie ( DSPACE_XSRF_COOKIE ) ;
192163 } ) ;
193- }
194- // Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
195- Cypress . Commands . add ( 'generateViewEvent' , generateViewEvent ) ;
196164
165+ // return the generated token wrapped in a chainable
166+ return cy . wrap ( csrfToken ) ;
167+ }
168+ // Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
169+ Cypress . Commands . add ( 'createCSRFCookie' , createCSRFCookie ) ;
0 commit comments