Skip to content
Open
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
14 changes: 14 additions & 0 deletions docs/developer-guide/local-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ For configuring plugins, see the [Configuring Plugins Section](plugins-documenta
}
```

- `enabled` - Optional, a plain boolean or a plugin expression string (same syntax as a plugin's `disablePluginIf`) to
conditionally apply the rule, e.g. based on the current user's groups. Rules without `enabled` are always applied.
Example:

```json
{
"urlPattern": ".*geoserver.*",
"params": {
"authkey": "${securityToken}"
},
"enabled": "{includes(state('usergroups'), 'editor')}"
}
```

!!! 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.

Expand Down
21 changes: 18 additions & 3 deletions web/client/utils/SecurityUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import castArray from "lodash/castArray";

import {setStore as stateSetStore, getState} from "./StateUtils";
import { parseUrl, WMS_GET_CAPABILITIES_VERSION } from '../api/WMS';
import { getMonitoredState, handleExpression } from './PluginsUtils';

export const USER_GROUP_ALL = 'everyone';

Expand Down Expand Up @@ -248,6 +249,20 @@ export const convertAuthenticationRulesToRequestConfiguration = (authRules = [])
}).filter(rule => rule !== null);
};

/**
* Checks a request configuration rule's `enabled` property.
* `enabled` can be a plain boolean or a plugin expression string (e.g. `"{state('usergroups').includes('editor')}"`),
* evaluated with the same syntax used for `cfg.disablePluginIf` (see `PluginsUtils.handleExpression`).
* Rules without an `enabled` property are always applied.
* @param {object} rule the request configuration rule
* @returns {boolean} true if the rule's `enabled` resolves to a truthy value
*/
const isRuleEnabled = (rule) => {
const enabled = rule?.enabled ?? true;
const monitoredState = getMonitoredState(getState());
return !!handleExpression((path) => get(monitoredState, path), undefined, enabled);
};

/**
* Gets all request configuration rules from Redux state or config
* Automatically converts authenticationRules to new format if requestsConfigurationRules is missing
Expand All @@ -257,19 +272,19 @@ export const getRequestConfigurationRules = () => {
// First try to get from Redux state (if available)
const stateRules = get(getState(), 'security.rules', []);
if (!isEmpty(stateRules)) {
return stateRules;
return stateRules.filter(isRuleEnabled);
}

// Try to get new format from config
const configRules = ConfigUtils.getConfigProp('requestsConfigurationRules');
if (!isEmpty(configRules)) {
return configRules;
return configRules.filter(isRuleEnabled);
}

// If new format is missing, convert old authenticationRules format
const authRules = ConfigUtils.getConfigProp('authenticationRules');
if (!isEmpty(authRules)) {
return convertAuthenticationRulesToRequestConfiguration(authRules);
return convertAuthenticationRulesToRequestConfiguration(authRules).filter(isRuleEnabled);
}

// No rules found
Expand Down
45 changes: 45 additions & 0 deletions web/client/utils/__tests__/SecurityUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,51 @@ describe('Test security utils methods', () => {
expect(result.length).toBe(2);
expect(result[0].urlPattern).toBe('.*geoserver.*');
});

it('should keep rules without an enabled property', () => {
const rulesInConfig = [
{ urlPattern: '.*api.*', headers: { 'Authorization': 'Bearer ${securityToken}' } }
];
setSecurityInfo({});
ConfigUtils.setConfigProp('requestsConfigurationRules', rulesInConfig);

const result = SecurityUtils.getRequestConfigurationRules();
expect(result).toEqual(rulesInConfig);
});

it('should filter out rules with enabled: false', () => {
const rulesInConfig = [
{ urlPattern: '.*api.*', enabled: false, headers: { 'Authorization': 'Bearer ${securityToken}' } }
];
setSecurityInfo({});
ConfigUtils.setConfigProp('requestsConfigurationRules', rulesInConfig);

const result = SecurityUtils.getRequestConfigurationRules();
expect(result.length).toBe(0);
});

it('should filter out rules whose enabled expression resolves to false', () => {
const rulesInConfig = [
{ urlPattern: '.*api.*', enabled: "{includes(state('usergroups'), 'editor')}" }
];
setSecurityInfo({ user: { groups: { group: [] } } });
ConfigUtils.setConfigProp('requestsConfigurationRules', rulesInConfig);

const result = SecurityUtils.getRequestConfigurationRules();
expect(result.length).toBe(0);
});

it('should keep rules whose enabled expression resolves to true against usergroups state', () => {
const rulesInConfig = [
{ urlPattern: '.*api.*', enabled: "{includes(state('usergroups'), 'editor')}" }
];
setSecurityInfo({ user: { groups: { group: [{ groupName: 'editor', enabled: true }] } } });
ConfigUtils.setConfigProp('requestsConfigurationRules', rulesInConfig);

const result = SecurityUtils.getRequestConfigurationRules();
expect(result.length).toBe(1);
expect(result[0].urlPattern).toBe('.*api.*');
});
});

describe('getAuthKeyParameter', () => {
Expand Down
Loading