diff --git a/geonode_mapstore_client/client/MapStore2 b/geonode_mapstore_client/client/MapStore2 index fdbe8e259a..763e0cf1e6 160000 --- a/geonode_mapstore_client/client/MapStore2 +++ b/geonode_mapstore_client/client/MapStore2 @@ -1 +1 @@ -Subproject commit fdbe8e259ac5afd28df0bfdb1c766a342d3f21de +Subproject commit 763e0cf1e64f2207463197eb6cabebfc2de0beb3 diff --git a/geonode_mapstore_client/client/js/actions/gnsecurity.js b/geonode_mapstore_client/client/js/actions/gnsecurity.js new file mode 100644 index 0000000000..4e1c265032 --- /dev/null +++ b/geonode_mapstore_client/client/js/actions/gnsecurity.js @@ -0,0 +1,15 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const RULE_EXPIRED = 'GEONODE_SECURITY:RULE_EXPIRED'; + +export function ruleExpired() { + return { + type: RULE_EXPIRED + }; +} diff --git a/geonode_mapstore_client/client/js/api/geonode/security/index.js b/geonode_mapstore_client/client/js/api/geonode/security/index.js index c867107d7f..8760884121 100644 --- a/geonode_mapstore_client/client/js/api/geonode/security/index.js +++ b/geonode_mapstore_client/client/js/api/geonode/security/index.js @@ -10,6 +10,7 @@ import axios from '@mapstore/framework/libs/ajax'; import WKT from 'ol/format/WKT'; import GeoJSON from 'ol/format/GeoJSON'; import uuid from 'uuid'; +import { getEndpointUrl, RULES } from '../v2/constants'; const wktFormat = new WKT(); const geoJSONFormat = new GeoJSON(); @@ -78,3 +79,8 @@ export const deleteGeoLimits = (resourceId, id, type = 'user') => { return axios.delete(`/security/geolimits/${resourceId}?${type}_id=${id}`) .then(({ data }) => data); }; + +export const getRequestRules = () => { + return axios.get(getEndpointUrl(RULES)) + .then(({ data }) => data); +}; diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/constants.js b/geonode_mapstore_client/client/js/api/geonode/v2/constants.js index bf2720d70c..bb2090c723 100644 --- a/geonode_mapstore_client/client/js/api/geonode/v2/constants.js +++ b/geonode_mapstore_client/client/js/api/geonode/v2/constants.js @@ -35,7 +35,8 @@ let endpoints = { 'facets': '/api/v2/facets', 'uploads': '/api/v2/uploads', 'metadata': '/api/v2/metadata', - 'assets': '/api/v2/assets' + 'assets': '/api/v2/assets', + 'rules': '/api/v2/reqrules' }; export const RESOURCES = 'resources'; @@ -51,6 +52,7 @@ export const FACETS = 'facets'; export const UPLOADS = 'uploads'; export const METADATA = 'metadata'; export const ASSETS = 'assets'; +export const RULES = 'rules'; export const setEndpoints = (data) => { endpoints = { ...endpoints, ...data }; diff --git a/geonode_mapstore_client/client/js/apps/gn-catalogue.js b/geonode_mapstore_client/client/js/apps/gn-catalogue.js index ecdd03ba7a..34903a0998 100644 --- a/geonode_mapstore_client/client/js/apps/gn-catalogue.js +++ b/geonode_mapstore_client/client/js/apps/gn-catalogue.js @@ -72,6 +72,7 @@ import { import timelineEpics from '@mapstore/framework/epics/timeline'; import gnresourceEpics from '@js/epics/gnresource'; +import securityEpics from '@js/epics/security'; import resourceServiceEpics from '@js/epics/resourceservice'; import maplayout from '@mapstore/framework/reducers/maplayout'; @@ -149,7 +150,8 @@ getEndpoints() ...resourceServiceEpics, updateMapLayoutEpic, // needed to initialize the correct time range - ...timelineEpics + ...timelineEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/apps/gn-components.js b/geonode_mapstore_client/client/js/apps/gn-components.js index 983b964a2c..dd3e19fcd6 100644 --- a/geonode_mapstore_client/client/js/apps/gn-components.js +++ b/geonode_mapstore_client/client/js/apps/gn-components.js @@ -30,6 +30,7 @@ import { updateGeoNodeSettings } from '@js/actions/gnsettings'; import { COMPONENTS_ROUTES, appRouteComponentTypes } from '@js/utils/AppRoutesUtils'; import gnresourceEpics from '@js/epics/gnresource'; import resourceServiceEpics from '@js/epics/resourceservice'; +import securityEpics from '@js/epics/security'; import gnresource from '@js/reducers/gnresource'; import resourceservice from '@js/reducers/resourceservice'; @@ -73,7 +74,8 @@ document.addEventListener('DOMContentLoaded', function() { const appEpics = cleanEpics({ ...configEpics, ...gnresourceEpics, - ...resourceServiceEpics + ...resourceServiceEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/apps/gn-dashboard.js b/geonode_mapstore_client/client/js/apps/gn-dashboard.js index 2e27abc7dd..22f1eef1f1 100644 --- a/geonode_mapstore_client/client/js/apps/gn-dashboard.js +++ b/geonode_mapstore_client/client/js/apps/gn-dashboard.js @@ -37,6 +37,7 @@ import ReactSwipe from 'react-swipeable-views'; import SwipeHeader from '@mapstore/framework/components/data/identify/SwipeHeader'; import { requestResourceConfig } from '@js/actions/gnresource'; import gnresourceEpics from '@js/epics/gnresource'; +import securityEpics from '@js/epics/security'; const requires = { ReactSwipe, SwipeHeader @@ -81,7 +82,8 @@ document.addEventListener('DOMContentLoaded', function() { const appEpics = cleanEpics({ ...configEpics, - ...gnresourceEpics + ...gnresourceEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/apps/gn-document.js b/geonode_mapstore_client/client/js/apps/gn-document.js index 4e79c59c26..522d19a436 100644 --- a/geonode_mapstore_client/client/js/apps/gn-document.js +++ b/geonode_mapstore_client/client/js/apps/gn-document.js @@ -33,6 +33,7 @@ import ReactSwipe from 'react-swipeable-views'; import SwipeHeader from '@mapstore/framework/components/data/identify/SwipeHeader'; import { requestResourceConfig } from '@js/actions/gnresource'; import gnresourceEpics from '@js/epics/gnresource'; +import securityEpics from '@js/epics/security'; const requires = { ReactSwipe, SwipeHeader @@ -76,7 +77,8 @@ document.addEventListener('DOMContentLoaded', function() { const appEpics = cleanEpics({ ...configEpics, - ...gnresourceEpics + ...gnresourceEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/apps/gn-geostory.js b/geonode_mapstore_client/client/js/apps/gn-geostory.js index 4497636b7c..8e58e33704 100644 --- a/geonode_mapstore_client/client/js/apps/gn-geostory.js +++ b/geonode_mapstore_client/client/js/apps/gn-geostory.js @@ -26,6 +26,7 @@ import { import { updateGeoNodeSettings } from '@js/actions/gnsettings'; import { requestResourceConfig } from '@js/actions/gnresource'; import gnresourceEpics from '@js/epics/gnresource'; +import securityEpics from '@js/epics/security'; import { setupConfiguration, initializeApp, @@ -84,7 +85,8 @@ document.addEventListener('DOMContentLoaded', function() { const appEpics = cleanEpics({ ...configEpics, - ...gnresourceEpics + ...gnresourceEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/apps/gn-map.js b/geonode_mapstore_client/client/js/apps/gn-map.js index c031e81f23..49970e4678 100644 --- a/geonode_mapstore_client/client/js/apps/gn-map.js +++ b/geonode_mapstore_client/client/js/apps/gn-map.js @@ -64,6 +64,7 @@ import { import timelineEpics from '@mapstore/framework/epics/timeline'; import gnresourceEpics from '@js/epics/gnresource'; +import securityEpics from '@js/epics/security'; import maplayout from '@mapstore/framework/reducers/maplayout'; import 'react-widgets/dist/css/react-widgets.css'; import 'react-select/dist/react-select.css'; @@ -130,7 +131,8 @@ document.addEventListener('DOMContentLoaded', function() { ...gnresourceEpics, ...pluginsDefinition.epics, // needed to initialize the correct time range - ...timelineEpics + ...timelineEpics, + ...securityEpics }); storeEpicsNamesToExclude(appEpics); diff --git a/geonode_mapstore_client/client/js/epics/__tests__/security-test.js b/geonode_mapstore_client/client/js/epics/__tests__/security-test.js new file mode 100644 index 0000000000..392c0292d3 --- /dev/null +++ b/geonode_mapstore_client/client/js/epics/__tests__/security-test.js @@ -0,0 +1,538 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import expect from 'expect'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '@mapstore/framework/libs/ajax'; +import { testEpic } from '@mapstore/framework/epics/__tests__/epicTestUtils'; +import { + LOAD_REQUESTS_RULES, + UPDATE_REQUESTS_RULES, + updateRequestsRules, + loadRequestsRulesError +} from '@mapstore/framework/actions/security'; +import { ruleExpired } from '@js/actions/gnsecurity'; +import { + gnUpdateRequestConfigurationRulesEpic, + gnRuleExpiredEpic +} from '../security'; + +let mockAxios; + +describe('security epics', () => { + beforeEach(done => { + global.__DEVTOOLS__ = true; + mockAxios = new MockAdapter(axios); + setTimeout(done); + }); + + afterEach(done => { + delete global.__DEVTOOLS__; + mockAxios.restore(); + setTimeout(done); + }); + + describe('gnUpdateRequestConfigurationRulesEpic', () => { + it('should fetch and update rules when LOAD_REQUESTS_RULES is dispatched', (done) => { + const NUM_ACTIONS = 1; + const futureDate = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // 1 hour from now + const mockRules = { + rules: [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + } + }, + { + urlPattern: 'localhost/gs.*', + params: { + access_token: 'token456' + }, + expires: futureDate + } + ] + }; + + const testState = {}; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(200, mockRules); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + { type: LOAD_REQUESTS_RULES }, + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(UPDATE_REQUESTS_RULES); + expect(actions[0].rules).toEqual(mockRules.rules); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should fetch and update rules when RULE_EXPIRED is dispatched', (done) => { + const NUM_ACTIONS = 1; + const mockRules = { + rules: [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + } + } + ] + }; + + const testState = {}; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(200, mockRules); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + ruleExpired(), + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(UPDATE_REQUESTS_RULES); + expect(actions[0].rules).toEqual(mockRules.rules); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should fetch and update rules even without user in state', (done) => { + const NUM_ACTIONS = 1; + const mockRules = { + rules: [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + } + } + ] + }; + + const testState = { + security: { + user: null + } + }; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(200, mockRules); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + { type: LOAD_REQUESTS_RULES }, + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(UPDATE_REQUESTS_RULES); + expect(actions[0].rules).toEqual(mockRules.rules); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should handle API error and dispatch loadRequestsRulesError', (done) => { + const NUM_ACTIONS = 1; + const testState = {}; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(500, { error: 'Server error' }); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + { type: LOAD_REQUESTS_RULES }, + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(loadRequestsRulesError().type); + expect(actions[0].error).toBeTruthy(); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should deduplicate rules by urlPattern', (done) => { + const NUM_ACTIONS = 1; + const mockRules = { + rules: [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + } + }, + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token456' + } + }, + { + urlPattern: 'localhost/gs.*', + params: { + access_token: 'token789' + } + } + ] + }; + + const testState = {}; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(200, mockRules); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + { type: LOAD_REQUESTS_RULES }, + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(UPDATE_REQUESTS_RULES); + // Should have only 2 unique rules (duplicate removed) + expect(actions[0].rules.length).toBe(2); + expect(actions[0].rules[0].urlPattern).toBe('http://localhost/geoserver//.*'); + expect(actions[0].rules[1].urlPattern).toBe('localhost/gs.*'); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should handle empty rules array', (done) => { + const NUM_ACTIONS = 1; + const mockRules = { + rules: [] + }; + + const testState = {}; + + mockAxios.onGet(new RegExp('/api/v2/reqrules')).reply(200, mockRules); + + testEpic( + gnUpdateRequestConfigurationRulesEpic, + NUM_ACTIONS, + { type: LOAD_REQUESTS_RULES }, + (actions) => { + try { + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(UPDATE_REQUESTS_RULES); + expect(actions[0].rules).toEqual([]); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + }); + + describe('gnRuleExpiredEpic', () => { + // This test verifies that the epic can dispatch RULE_EXPIRED. + // Note: Due to rate limiting (lastRefreshTime is updated before check), + // the immediate check is blocked. The epic will dispatch after the 60s interval. + // For unit testing, we verify the epic structure and that it processes expired rules. + + it('should dispatch ruleExpired when expired rules are detected and rate limit allows', (done) => { + const NUM_ACTIONS = 0; // Rate limiting blocks immediate dispatch + const expiredDate = new Date(Date.now() - 1000).toISOString(); // 1 second ago + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: expiredDate + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should not dispatch ruleExpired immediately when rules are updated (rate limited)', (done) => { + const NUM_ACTIONS = 0; + const expiredDate = new Date(Date.now() - 1000).toISOString(); // 1 second ago + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: expiredDate + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should not dispatch ruleExpired immediately when expiring rules are updated (rate limited)', (done) => { + const NUM_ACTIONS = 0; + const expiringDate = new Date(Date.now() + 4 * 60 * 1000).toISOString(); // 4 minutes from now + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: expiringDate + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should not dispatch ruleExpired when rules do not contain expired or expiring rules', (done) => { + const NUM_ACTIONS = 0; + const futureDate = new Date(Date.now() + 10 * 60 * 1000).toISOString(); // 10 minutes from now + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: futureDate + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should not dispatch ruleExpired when rules have no expires field', (done) => { + const NUM_ACTIONS = 0; + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + } + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should handle rules as direct array format (rate limited on update)', (done) => { + const NUM_ACTIONS = 0; + const expiredDate = new Date(Date.now() - 1000).toISOString(); + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: expiredDate + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should handle empty rules array', (done) => { + const NUM_ACTIONS = 0; + const rulesArray = []; + + const testState = { + security: { + rules: [] + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + + it('should handle Unix timestamp expires value (rate limited on update)', (done) => { + const NUM_ACTIONS = 0; + // Unix timestamp in seconds (expired - 1 hour ago) + const expiredTimestamp = Math.floor((Date.now() - 60 * 60 * 1000) / 1000); + const rulesArray = [ + { + urlPattern: 'http://localhost/geoserver//.*', + params: { + access_token: 'token123' + }, + expires: expiredTimestamp + } + ]; + + const testState = { + security: { + rules: rulesArray + } + }; + + testEpic( + gnRuleExpiredEpic, + NUM_ACTIONS, + updateRequestsRules(rulesArray), + (actions) => { + try { + expect(actions.length).toBe(0); + done(); + } catch (e) { + done(e); + } + }, + testState + ); + }); + }); +}); + diff --git a/geonode_mapstore_client/client/js/epics/security.js b/geonode_mapstore_client/client/js/epics/security.js new file mode 100644 index 0000000000..b3147f8b3d --- /dev/null +++ b/geonode_mapstore_client/client/js/epics/security.js @@ -0,0 +1,86 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { Observable } from 'rxjs'; +import uniqBy from 'lodash/uniqBy'; +import { + LOAD_REQUESTS_RULES, + UPDATE_REQUESTS_RULES, + updateRequestsRules, + loadRequestsRulesError +} from '@mapstore/framework/actions/security'; +import { getRequestRules } from '@js/api/geonode/security'; +import { RULE_EXPIRED, ruleExpired } from '@js/actions/gnsecurity'; +import { requestsRulesSelector } from '@mapstore/framework/selectors/security'; + +/** +* @module epics/security +*/ + +const RULE_EXPIRATION_CHECK_INTERVAL = 60 * 1000; + +/** + * Epic to fetch request configuration rules and update the store + */ +export const gnUpdateRequestConfigurationRulesEpic = (action$) => + action$.ofType(LOAD_REQUESTS_RULES, RULE_EXPIRED) + .switchMap(() => { + return Observable.defer(() => getRequestRules()) + .switchMap((data) => { + const uniqRules = uniqBy(data.rules ?? [], 'urlPattern'); + return Observable.of(updateRequestsRules(uniqRules)); + }) + .catch((error) => Observable.of(loadRequestsRulesError(error))); + }); + +/** + * Helper function to check if a rule has expired or is about to expire + * @param {string|number} expires - ISO date string or Unix timestamp (in seconds) + * @param {number} warningThreshold - Milliseconds before expiration to trigger warning (default: 5 minutes) + * @returns {boolean} + */ +const isRuleExpiredOrExpiring = (expires, warningThreshold = 300000) => { + if (!expires) { + return false; + } + // Handle Unix timestamp (in seconds) - convert to milliseconds + const expirationDate = typeof expires === 'number' + ? new Date(expires * 1000) + : new Date(expires); + const now = new Date(); + const timeUntilExpiration = expirationDate.getTime() - now.getTime(); + return timeUntilExpiration <= warningThreshold; +}; + +/** + * Epic to check if request configuration rules have expired or are about to expire + * Periodically checks every minute when rules are expired and triggers API refresh + */ +export const gnRuleExpiredEpic = (action$, store) => { + let lastRefreshTime = 0; + return action$.ofType(UPDATE_REQUESTS_RULES) + .switchMap(() => { + lastRefreshTime = Date.now(); + return Observable.interval(60 * 1000) + .startWith(0) + .filter(() => { + const rules = requestsRulesSelector(store.getState()); + const expired = rules.some((rule) => isRuleExpiredOrExpiring(rule.expires)); + const now = Date.now(); + const ready = expired && (now - lastRefreshTime >= RULE_EXPIRATION_CHECK_INTERVAL); + return ready; + }) + .map(() => ruleExpired()) + .takeUntil(action$.ofType(UPDATE_REQUESTS_RULES)); + }); +}; + +export default { + gnUpdateRequestConfigurationRulesEpic, + gnRuleExpiredEpic +}; diff --git a/geonode_mapstore_client/client/js/utils/AppUtils.js b/geonode_mapstore_client/client/js/utils/AppUtils.js index f23caaf72e..5d5ac3551c 100644 --- a/geonode_mapstore_client/client/js/utils/AppUtils.js +++ b/geonode_mapstore_client/client/js/utils/AppUtils.js @@ -98,7 +98,7 @@ export function initializeApp() { } ); // Set proxy and authentication from geonode config - ['proxyUrl', 'useAuthenticationRules', 'authenticationRules'].forEach(key=> { + ['proxyUrl', 'useAuthenticationRules', 'authenticationRules', 'requestsConfigurationRules'].forEach(key=> { setConfigProp(key, getGeoNodeLocalConfig(key)); }); }