Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions docs/developer-guide/integrations/geoserver.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,25 +158,30 @@ The last step is to configure MapStore to use the authkey with the configured in

```javascript
//...
"useAuthenticationRules": true,
"authenticationRules": [{
"requestsConfigurationRules": [
{
"urlPattern": ".*geostore.*",
"method": "bearer"
}, {
"headers": {
"Authorization": "Bearer ${securityToken}"
}
},
{
"urlPattern": "\\/geoserver/.*",
"authkeyParamName": "authkey",
"method": "authkey"
}],
"params": {
"authkey": "${securityToken}"
}
}
],
//...
```

- Verify that "useAuthenticationRules" is set to `true`
- `authenticationRules` array should contain 2 rules:
- The first rule should already be present, and defines the authentication method used internally in mapstore
- Note: The new `requestsConfigurationRules` system is always active when rules are present, no flag needed
- `requestsConfigurationRules` array should contain 2 rules:
- The first rule should already be present, and defines the authentication method used internally in mapstore (Bearer token)
- The second rule (the one you need to add) should be added and defines how to authenticate to GeoServer:
- `urlPattern`: is a regular expression that identifies the request url where to apply the rule
- `method`: set it to `authkey` to use the authentication filter you just created in Geoserver.
- `authkeyParamName`: is the name of the authkey parameter defined in GeoServer (set to `authkey` by default)
- `params`: use query parameters for authkey authentication
- `authkey`: the name of the parameter (must match the one in GeoServer configuration, default is `authkey`)

### Advantages of user integration

Expand Down
99 changes: 74 additions & 25 deletions docs/developer-guide/local-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,30 @@ This is the main structure:
// path to the translation files directory (if different from default)
"translationsPath",
// if true, every ajax and mapping request will be authenticated with the configurations if match a rule (default: true)
"useAuthenticationRules": true
// the authentication rules to match
"authenticationRules": [
{ // every rule has a `urlPattern` regex to match
"urlPattern": ".*geostore.*",
// and a authentication `method` to use (basic, authkey, browserWithCredentials, header)
"method": "basic"
}, {
"urlPattern": "\\/geoserver.*",
"method": "authkey"
}],
// the request configuration rules to match
"requestsConfigurationRules": [
{ // every rule has a `urlPattern` regex to match
"urlPattern": ".*geostore.*",
// headers to add to matching requests
"headers": {
"Authorization": "Bearer ${securityToken}"
}
}, {
"urlPattern": "\\/geoserver/.*",
// parameters to add to matching requests
"params": {
"authkey": "${securityToken}"
}
}, {
"urlPattern": ".*azure-blob.*",
// expiration timestamp (optional, Unix timestamp in seconds)
"expires": 1735689600,
// parameters can be used for SAS tokens
"params": {
"sv": "2024-11-04",
"sig": "${sasToken}"
}
}],
// flag for postponing mapstore 2 load time after theme
"loadAfterTheme": false,
// if defined, WMS layer styles localization will be added
Expand Down Expand Up @@ -149,23 +162,59 @@ For configuring plugins, see the [Configuring Plugins Section](plugins-documenta
- `initialState`: is an object that will initialize the state with some default values and this WILL OVERRIDE the initialState imposed by plugins & reducers.
- `projectionDefs`: is an array of objects that contain definitions for Coordinate Reference Systems
- `gridFiles`: is an object that contains definitions for grid files used in coordinate transformations
- `useAuthenticationRules`: if this flag is set to true, the `authenticationRules` will be used to authenticate every ajax and mapping request. If the flag is set to false, the `authenticationRules` will be ignored.
- `authenticationRules`: is an array of objects that contain rules to match for authentication. Each rule has a `urlPattern` regex to match and a `method` to use (`basic`, `authkey`, `header`, `browserWithCredentials`). If the URL of a request matches the `urlPattern` of a rule, the `method` will be used to authenticate the request. The `method` can be:
- `basic` will use the basic authentication method getting the credentials from the user that logged in (adding the header `Authorization` `Basic <base64(username:password)>` to the request). ***Note**: this method is not implemented for image tile requests (e.g. layers) but only for ajax requests.*
- `authkey` will use the authkey method getting the credentials from the user that logged in. The token of the current MapStore session will be used as the authkey value, so this works only with the geoserver integration.
- `bearer` will use the header `Authorization` `Bearer <token>` getting the credentials from the user that logged in. The token of the current MapStore session will be used as the bearer value, so this works only with the geoserver integration.
- `header` will use the header method getting the credentials from the user that logged in. You can add an `headers` object containing the static headers to this rule to specify witch headers to use. e.g.
- `browserWithCredentials` will add the `withCredentials` parameter to ajax requests, so the browser will send the cookies and the authentication headers to the server. This method is useful when you have a proxy that needs to authenticate the user. ***Note**: this method is not implemented for image tile requests (e.g. layers) but only for ajax requests.*

```json
- `useAuthenticationRules` (deprecated): if this flag is set to true, legacy `authenticationRules` will be used. The new `requestsConfigurationRules` system does not require this flag and is always active when rules are present.
- `requestsConfigurationRules`: is an array of objects that contain rules to match for request configuration. Each rule has a `urlPattern` regex to match and either `headers`, `params`, or `withCredentials` configuration. If the URL of a request matches the `urlPattern` of a rule, the configuration will be applied to the request.

**Available variable for template substitution (ES6 template syntax `${variable}`):**
- `${securityToken}` - The current MapStore session token (automatically replaced)

**Configuration options:**
- `headers` - Object containing HTTP headers to add to matching requests. Example:

```json
{
"urlPattern": ".*geostore.*",
"method": "header",
"headers": {
"X-Auth-Token": "mytoken"
}
"urlPattern": ".*geostore.*",
"headers": {
"Authorization": "Bearer ${securityToken}"
}
}
```

- `params` - Object containing query parameters to add to matching requests. Example:

```json
{
"urlPattern": "\\/geoserver/.*",
"params": {
"authkey": "${securityToken}"
}
}
```

- `withCredentials` - Boolean to enable sending credentials with requests (useful with proxies):

```json
{
"urlPattern": ".*internal-api.*",
"withCredentials": true
}
```

- `expires` - Optional Unix timestamp (in seconds) for automatic rule expiration. Example:

```json
{
"urlPattern": ".*azure-blob.*",
"expires": 1735689600,
"params": {
"sv": "2024-11-04",
"sig": "token"
}
}
```

!!! note "Backward Compatibility"
The old `useAuthenticationRules` and `authenticationRules` configuration still works and will be automatically converted to the new format. However, the new format is recommended for better flexibility and features like expiration support.

### initialState configuration

Expand Down
39 changes: 38 additions & 1 deletion web/client/actions/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import AuthenticationAPI from '../api/GeoStoreDAO';
import {setCredentials, getToken, getRefreshToken} from '../utils/SecurityUtils';
import {encodeUTF8} from '../utils/EncodeUtils';


export const CHECK_LOGGED_USER = 'CHECK_LOGGED_USER';
export const LOGIN_SUBMIT = 'LOGIN_SUBMIT';
export const LOGIN_PROMPT_CLOSED = "LOGIN:LOGIN_PROMPT_CLOSED";
Expand All @@ -34,6 +33,11 @@ export const SET_CREDENTIALS = 'SECURITY:SET_CREDENTIALS';
export const CLEAR_SECURITY = 'SECURITY:CLEAR_SECURITY';
export const SET_PROTECTED_SERVICES = 'SECURITY:SET_PROTECTED_SERVICES';
export const REFRESH_SECURITY_LAYERS = 'SECURITY:REFRESH_SECURITY_LAYERS';

export const UPDATE_REQUESTS_RULES = 'SECURITY:UPDATE_REQUESTS_RULES';
export const LOAD_REQUESTS_RULES = 'SECURITY:LOAD_REQUESTS_RULES';
export const LOAD_REQUESTS_RULES_ERROR = 'SECURITY:LOAD_REQUESTS_RULES_ERROR';

export function loginSuccess(userDetails, username, password, authProvider) {
return {
type: LOGIN_SUCCESS,
Expand Down Expand Up @@ -229,3 +233,36 @@ export function refreshSecurityLayers() {
type: REFRESH_SECURITY_LAYERS
};
}

/**
* Updates the request configuration rules
* @param {Array} rules - Array of request configuration rules
* @param {boolean} enabled - Whether request configuration is enabled
*/
export const updateRequestsRules = (rules) => {
return {
type: UPDATE_REQUESTS_RULES,
rules
};
};

/**
* Starts loading request configuration rules
*/
export const loadRequestsRules = (rules) => {
return {
type: LOAD_REQUESTS_RULES,
rules
};
};

/**
* Error loading request configuration rules
* @param {Error} error - The error that occurred
*/
export const loadRequestsRulesError = (error) => {
return {
type: LOAD_REQUESTS_RULES_ERROR,
error
};
};
4 changes: 1 addition & 3 deletions web/client/api/ArcGIS.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/

import { getAuthorizationBasic } from '../utils/SecurityUtils';
import axios from '../libs/ajax';
import { reprojectBbox } from '../utils/CoordinatesUtils';
import trimEnd from 'lodash/trimEnd';
Expand Down Expand Up @@ -89,14 +88,13 @@ export const searchAndPaginate = (records, params) => {
};
const getData = (url, params = {}) => {
const protectedId = params?.info?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
const request = _cache[url]
? () => Promise.resolve(_cache[url])
: () => axios.get(url, {
params: {
f: 'json'
},
headers
_msAuthSourceId: protectedId
}).then(({ data }) => {
_cache[url] = data;
return data;
Expand Down
8 changes: 3 additions & 5 deletions web/client/api/CSW.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { extractCrsFromURN, makeBboxFromOWS, makeNumericEPSG, getExtentFromNorma
import WMS from "../api/WMS";
import { THREE_D_TILES, getCapabilities } from './ThreeDTiles';
import { getDefaultUrl } from '../utils/URLUtils';
import { getAuthorizationBasic } from '../utils/SecurityUtils';

export const parseUrl = (url) => {
const parsed = urlUtil.parse(getDefaultUrl(url), true);
Expand Down Expand Up @@ -513,12 +512,11 @@ const Api = {
getRecords: function(url, startPosition, maxRecords, text, options) {
const body = constructXMLBody(startPosition, maxRecords, text, options);
const protectedId = options?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.post(parseUrl(url), body, {
headers: {
'Content-Type': 'application/xml',
...headers
}
'Content-Type': 'application/xml'
},
_msAuthSourceId: protectedId
}).then((response) => {
const { error, _dcRef, result } = parseCSWResponse(response) || {};
if (result) {
Expand Down
4 changes: 1 addition & 3 deletions web/client/api/TMS.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/
import xml2js from 'xml2js';
import axios from '../libs/ajax';
import { getAuthorizationBasic } from '../utils/SecurityUtils';

/**
* Common requests to TMS services.
Expand All @@ -21,8 +20,7 @@ import { getAuthorizationBasic } from '../utils/SecurityUtils';
*/
export const getTileMap = (url, options) => {
const protectedId = options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.get(url, {headers})
return axios.get(url, {_msAuthSourceId: protectedId})
.then(response => {
return new Promise((resolve) => {
xml2js.parseString(response.data, { explicitArray: false }, (ignore, result) => resolve(result));
Expand Down
4 changes: 1 addition & 3 deletions web/client/api/ThreeDTiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import axios from '../libs/ajax';
import { convertRadianToDegrees } from '../utils/CoordinatesUtils';
import { METERS_PER_UNIT } from '../utils/MapUtils';
import { logError } from '../utils/DebugUtils';
import { getAuthorizationBasic } from '../utils/SecurityUtils';

// converts the boundingVolume of the root tileset to a valid layer bbox
function tilesetToBoundingBox(Cesium, tileset) {
Expand Down Expand Up @@ -140,8 +139,7 @@ function extractCapabilities(tileset) {
*/
export const getCapabilities = (url, info) => {
const protectedId = info?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.get(url, {headers})
return axios.get(url, {_msAuthSourceId: protectedId})
.then(({ data }) => {
return extractCapabilities(data).then((properties) => ({ tileset: data, ...properties }));
}).catch((e) => {
Expand Down
4 changes: 1 addition & 3 deletions web/client/api/WFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {toOGCFilterParts} from '../utils/FilterUtils';
import { getDefaultUrl } from '../utils/URLUtils';
import { castArray } from 'lodash';
import { isValidGetFeatureInfoFormat } from '../utils/WMSUtils';
import { getAuthorizationBasic } from '../utils/SecurityUtils';

const capabilitiesCache = {};

Expand Down Expand Up @@ -140,8 +139,7 @@ export const getCapabilities = function(url, info) {
return Promise.resolve(cached.data);
}
const protectedId = info?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.get(getCapabilitiesURL(url, {headers}))
return axios.get(getCapabilitiesURL(url), {_msAuthSourceId: protectedId})
.then((response) => {
let json;
xml2js.parseString(response.data, { explicitArray: false, stripPrefix: true }, (ignore, result) => {
Expand Down
11 changes: 4 additions & 7 deletions web/client/api/WMS.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import axios from '../libs/ajax';
import { getConfigProp } from '../utils/ConfigUtils';
import { getWMSBoundingBox } from '../utils/CoordinatesUtils';
import { isValidGetMapFormat, isValidGetFeatureInfoFormat } from '../utils/WMSUtils';
import { getAuthorizationBasic } from '../utils/SecurityUtils';
const capabilitiesCache = {};

export const WMS_GET_CAPABILITIES_VERSION = '1.3.0';
Expand Down Expand Up @@ -160,12 +159,12 @@ export const getDimensions = (layer) => {
* - `Capability`: capability object that contains layers and requests formats
* - `Service`: service information object
*/
export const getCapabilities = (url, headers = {}) => {
export const getCapabilities = (url, {headers, params, _msAuthSourceId} = {}) => {
return axios.get(parseUrl(url, {
service: "WMS",
version: WMS_GET_CAPABILITIES_VERSION,
request: "GetCapabilities"
}), {headers}).then((response) => {
}), {headers, params, _msAuthSourceId}).then((response) => {
let json;
xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
json = result;
Expand Down Expand Up @@ -204,8 +203,7 @@ export const getRecords = (url, startPosition, maxRecords, text, options) => {
});
}
const protectedId = options?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return getCapabilities(url, headers)
return getCapabilities(url, {_msAuthSourceId: protectedId})
.then((json) => {
capabilitiesCache[url] = {
timestamp: new Date().getTime(),
Expand All @@ -215,13 +213,12 @@ export const getRecords = (url, startPosition, maxRecords, text, options) => {
});
};
export const describeLayers = (url, layers, security) => {
const headers = getAuthorizationBasic(security?.sourceId);
return axios.get(parseUrl(url, {
service: "WMS",
version: WMS_DESCRIBE_LAYER_VERSION,
layers: layers,
request: "DescribeLayer"
}), {headers}).then((response) => {
}), {_msAuthSourceId: security?.sourceId}).then((response) => {
let descriptions;
xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
descriptions = result && result.WMS_DescribeLayerResponse && result.WMS_DescribeLayerResponse.LayerDescription;
Expand Down
7 changes: 2 additions & 5 deletions web/client/api/WMTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
getDefaultStyleIdentifier,
getDefaultFormat
} from '../utils/WMTSUtils';
import { getAuthorizationBasic } from '../utils/SecurityUtils';

export const parseUrl = (url) => {
const parsed = urlUtil.parse(getDefaultUrl(url), true);
Expand Down Expand Up @@ -82,8 +81,7 @@ const Api = {
});
}
const protectedId = options?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.get(parseUrl(url), {headers}).then((response) => {
return axios.get(parseUrl(url), {_msAuthSourceId: protectedId}).then((response) => {
let json;
xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
json = result;
Expand All @@ -106,8 +104,7 @@ const Api = {
});
}
const protectedId = options?.options?.service?.protectedId;
let headers = getAuthorizationBasic(protectedId);
return axios.get(parseUrl(url), {headers}).then((response) => {
return axios.get(parseUrl(url), {_msAuthSourceId: protectedId}).then((response) => {
let json;
xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
json = result;
Expand Down
Loading