Policy Service
Policy Service validates JSON content against named policy rules before changes are persisted to managed, system, or internal repository resources.
- OSGi service class
-
org.forgerock.openidm.policy.PolicyService - OSGi persistent identifier
-
org.forgerock.openidm.policy - Configuration file
-
policy.json - Router mapping
-
/policy*
The Java service is a thin wrapper around the policy engine implemented in JavaScript.
The service registers a RequestHandler that delegates ACTION and READ requests to the script named by conf/policy.json (default policy.js).
The default policy.js ships with a built-in catalogue of policy functions and a processor.
The processor loads per-resource policy configuration, evaluates rules against the request content, and returns a list of failed policyRequirement codes.
A separate router filter, policyFilter.js, calls the validateObject action implicitly on every create and update request to managed/, system/, or repo/internal/*.
This is the primary path through which policy enforcement runs.
Policy Configuration
Wren:IDM loads conf/policy.json as the script configuration of PolicyService, treating it as a script descriptor (type, file or source) extended with policy-specific keys.
conf/policy.json{
"type" : "text/javascript",
"file" : "policy.js",
"additionalFiles" : [ ],
"resources" : [
{
"resource" : "repo/internal/user/*",
"properties" : [
{
"name" : "_id",
"policies" : [
{ "policyId" : "cannot-contain-characters",
"params" : { "forbiddenChars" : [ "/" ] } }
]
},
{
"name" : "password",
"policies" : [
{ "policyId" : "required" },
{ "policyId" : "not-empty" },
{ "policyId" : "at-least-X-capitals", "params" : { "numCaps" : 1 } },
{ "policyId" : "at-least-X-numbers", "params" : { "numNums" : 1 } },
{ "policyId" : "minimum-length", "params" : { "minLength" : 8 } }
]
}
]
}
]
}
type-
MIME type of the policy script. Type: String. Default:
text/javascript. file-
Path of the script file containing the policy implementation. The script is resolved through the standard Wren:IDM script registry. Type: String. Default:
policy.js. source-
Inline source code of the policy script, provided as an alternative to
file(the two are mutually exclusive). Use this to embed a policy script directly inconf/policy.jsonwithout a separate file. Type: String. additionalFiles-
Optional array of paths to additional policy scripts resolved against the project home directory (the runtime directory containing
conf/). An absolute path is used as-is. A relative path is resolved against project home, not against the script registry source directories. A file in<project>/script/must be referenced asscript/myFile.js, not as a bare name. Each file is read at activation time, its source code is exposed topolicy.jsthrough theadditionalPoliciesbinding, andpolicy.jsevaluates each file to register custom policy IDs. Type: Array of String. resources-
Array of per-resource policy configurations. Each entry binds a resource pattern to the list of properties and the policies that must hold for those properties. Type: Array.
Resource and Property Configuration
{
"resource" : "managed/user/*",
"properties" : [
{
"name" : "email",
"policies" : [
{ "policyId" : "valid-email-address-format" }
],
"conditionalPolicies" : [
{
"condition" : {
"type" : "text/javascript",
"source" : "fullObject.accountStatus === 'active'"
},
"dependencies" : [ "accountStatus" ],
"policies" : [
{ "policyId" : "required" }
]
}
],
"fallbackPolicies" : [
{ "policyId" : "not-empty" }
]
}
]
}
resource-
Resource pattern matched against the request resource path segment by segment, where
*is a wildcard at any segment position. Type: String. properties[]-
Policies attached to individual properties of the resource. Type: Array.
name-
Property name. Supports a slash-separated path (e.g.
mail/0) and a trailing[*]to apply policies to every element of an array property. Type: String. policies[]-
List of policies that always apply to the property. Each entry uses
policyIdto reference a built-in or custom policy and an optionalparamsobject whose contents depend on the policy. Type: Array. conditionalPolicies[]-
Optional list of
{ condition, dependencies, policies }blocks that add policies only when their condition script evaluates to a truthy value (see Conditional and Fallback Policies). Type: Array. fallbackPolicies[]-
Optional list of policies applied only when none of the
conditionalPoliciesmatched. Otherwise ignored. Type: Array.
The resource matcher requires the same number of slash-separated segments on both sides.
It does not support prefix-style globs such as managed/*.
To enforce policies on both collection-level and instance-level requests for the same resource type, add two separate entries: one with resource: managed/user and one with resource: managed/user/.
|
Built-in Policies
The default policy.js registers the following policies.
Each policy publishes one or more policyRequirement codes, which are returned in failure responses.
{
"policyId" : "minimum-length",
"params" : { "minLength" : 8 }
}
required-
Fails when the property value is
undefined. Always evaluated. Requirement code:REQUIRED. not-empty-
Fails when the value is
nullor an empty string/array. Skipped if the property is absent (validateOnlyIfPresentis set). Unlikerequired, this policy does not fail for missing properties. Requirement code:REQUIRED. unique-
Queries the parent collection (the resource path with the leaf segment removed) for a sibling whose value matches the property, using the filter
<propertyName> eq "<value>". The target resource must support_queryFilterqueries. This policy is suited tomanaged/andrepo/resources but fails at runtime onsystem/connectors that do not support filtered queries. Requirement code:UNIQUE. no-internal-user-conflict-
Runs the named query
credential-internaluser-queryagainstrepo/internal/userto detect username collisions. This named query must be defined in the repository configuration. It is included in all defaultconf/repo.*.jsonconfigurations shipped with Wren:IDM. Requirement code:UNIQUE. mapping-exists-
Reads
config/sync(the internal configuration resource backed byconf/sync.json) and checks that the supplied value is the name of a configured sync mapping. Requirement code:MAPPING_EXISTS. regexpMatches-
Tests the value against
params.regexp(with optionalparams.flags). Requirement code:MATCH_REGEXP. valid-type-
Compares the runtime type of the value (
null,array, ortypeofresult) againstparams.types. Requirement code:VALID_TYPE. valid-date-
Accepts any string parseable by
new Date(value). Requirement code:VALID_DATE. valid-email-address-format-
Tests against
/.@.\\..+/i. Requirement code:VALID_EMAIL_ADDRESS_FORMAT. valid-name-format-
Permits letters, hyphens, whitespace, apostrophes, and a fixed set of accented Unicode characters. Requirement code:
VALID_NAME_FORMAT. valid-phone-format-
Permits digits, spaces, parentheses, hyphens, and an optional leading
+. Requirement code:VALID_PHONE_FORMAT. at-least-X-capitals-
Requires at least
params.numCapscapital letters. Requirement code:AT_LEAST_X_CAPITAL_LETTERS. at-least-X-numbers-
Requires at least
params.numNumsdigits. Requirement code:AT_LEAST_X_NUMBERS. minimum-length-
Requires
value.length >= params.minLength. Requirement code:MIN_LENGTH. cannot-contain-characters-
Fails if any character in
params.forbiddenCharsappears in the value. Requirement code:CANNOT_CONTAIN_CHARACTERS. cannot-contain-others-
Fails if the value contains the value of any property listed in
params.disallowedFields. On the server, the persisted object is also read and any missingdisallowedFieldsvalues are filled in from it before the comparison. WhenclientValidationis enabled and this policy runs in the browser, the persisted object is not available. Only the in-flight object is used. Requirement code:CANNOT_CONTAIN_OTHERS. cannot-contain-duplicates-
Fails if a list value contains repeated entries. Requirement code:
CANNOT_CONTAIN_DUPLICATES. max-attempts-triggers-lock-cooldown-
Fails when the value exceeds
params.maxand the timestamp atparams.dateTimeFieldis within the lastparams.numMinutesminutes. The field named byparams.dateTimeFieldis read from the in-flight object and passed to JavaScript’snew Date()constructor. Any format accepted bynew Date()is valid, including ISO 8601 strings. Requirement code:NO_MORE_THAN_X_ATTEMPTS_WITHIN_Y_MINUTES.
Policies whose definition sets validateOnlyIfPresent (the default for most format/length checks) are skipped when the property value is undefined.
Explicit required or not-empty is required to reject missing values.
The setting validateOnlyIfPresent is part of the policy definition and cannot be overridden per-entry in conf/policy.json.
|
The clientValidation flag, set in the policy definition (not in conf/policy.json), determines whether the policy function is published to the UI for in-browser validation.
Failure messages are otherwise produced server-side only.
|
Implicit Policies for Managed Objects
For resources of the form managed/<object> and managed/<object>/<id>, the policy processor reads config/managed.
Before evaluating the configured properties[], it synthesizes additional policies from the schema of the matching managed object.
The implicit policies derived from each schema.properties entry are:
-
required– added when the property name is listed inschema.required. -
not-empty– added when the property type is a non-nullarray, or whenminLengthis greater than zero. -
minimum-length– added whenminLengthis set on a string property. -
regexpMatches– added withparams.regexp = patternwhenpatternis set on a string property. -
valid-type– always added, withparams.typesderived from the schema type (relationship types are mapped toobject).
Implicit policies are merged with the corresponding entry in conf/policy.json.
The merge follows these rules:
| Scenario | Outcome |
|---|---|
|
The schema-derived policy overwrites the configured entry. |
Schema-derived policy has a |
Appended to the configured |
Property exists in the schema only (not listed in |
The entire schema-derived entry is added, including its |
Property exists in both ( |
Schema-derived entries are merged with the configured ones. |
Property exists in both ( |
Schema-derived |
Schema-derived policies apply only to the managed/ resource family.
The matcher returns an empty list for repo/, system/, or any path with more than three segments (for example, managed/user/abc123/roles has four segments and is not matched).
|
Conditional and Fallback Policies
Each entry of conditionalPolicies[] defines a conditional policy block evaluated per property at validation time.
The array fallbackPolicies[] is a separate property-level sibling of conditionalPolicies[], not a field within each entry.
{
"name" : "email",
"policies" : [ ],
"conditionalPolicies" : [
{
"condition" : {
"type" : "text/javascript",
"source" : "fullObject.accountStatus === 'active'"
},
"dependencies" : [ "accountStatus" ],
"policies" : [
{ "policyId" : "required" }
]
}
],
"fallbackPolicies" : [
{ "policyId" : "not-empty" }
]
}
dependencies-
List of top-level property names that must be present on the in-flight object. If any name is missing, the entry is skipped. The check uses
hasOwnPropertyonfullObject, so only top-level keys are valid. The slash-path and[*]notation used inproperties[].nameis not supported independencies. Type: Array of String. condition-
Script descriptor evaluated through the
scriptaction. Valid descriptor keys are:-
type– required, e.g."text/javascript" -
source– inline script -
file– file path resolved through the script registry -
globals– a map whose entries are injected as named variables into the condition script scopeBefore evaluation,
policy.jssetsfullObjectdirectly on the condition descriptor object. It is then available as a named variable inside the condition script. A truthy result adds the entry’spoliciesto the validation set. At evaluation time,fullObjectcontains the full in-flight request body as submitted. Fields not included in the request body are not present infullObject. The complete set of named bindings available to the condition script isfullObjectplus any entries inglobals. Type: Object.
-
policies-
List of policy entries to apply when the condition evaluates to a truthy value. Type: Array.
The array fallbackPolicies[] is a property-level sibling of conditionalPolicies[].
It is used when none of the conditional entries matched:
fallbackPolicies-
List of policies applied only when none of the
conditionalPoliciesentries matched. Otherwise ignored. Type: Array.
The standard scripted-service bindings (request, context, resourceName, resourcePath) are not injected in this evaluation path.
|
Custom Policy Scripts
The additionalFiles configuration key registers extra policy IDs without modifying the default policy.js.
At service activation each entry of additionalFiles is treated as a project-relative path resolved through IdentityServer.getFileForProjectPath.
The default policy.js then eval()`s each entry.
During evaluation, the additional script calls `addPolicy() to register new policy IDs and declares their validation functions.
A custom additional-policy file must call addPolicy() (provided as an implicit function by the loader) to register each policy.
It must also declare its validation function as a named function declaration whose name matches policyExec.
That name must belong to a top-level function declaration (not a variable assignment or expression) in the same file.
The loader resolves the function by evaluating the policyExec name in the calling scope after eval().
The validation function receives four arguments:
-
fullObject– the in-flight request body -
value– the property value -
params– the policy params fromconf/policy.json -
property– the property name
It returns an empty array on success or an array of { "policyRequirement": "<CODE>", … } objects on failure.
/* addPolicy() is provided by the loader; do not declare a top-level policyConfig variable —
it would shadow policy.js's internal policy accumulator */
addPolicy({
policyId: "my-custom-policy",
policyExec: "myCustomPolicy",
policyRequirements: ["MY_CUSTOM_REQUIREMENT"],
validateOnlyIfPresent: true,
clientValidation: false
});
/* Validation function — must be a named function declaration, not a var assignment */
function myCustomPolicy(fullObject, value, params, property) {
if (/* value fails the check */) {
return [{ "policyRequirement": "MY_CUSTOM_REQUIREMENT" }];
}
return [];
}
Files referenced by additionalFiles are loaded with eval() and run with full server-side privileges.
The path is resolved relative to the project home and is not restricted to the script directory.
Treat additional policy files as trusted code.
|
To replace the entire policy engine rather than extend it, change "file" in conf/policy.json to point to a custom script name.
The script registry resolves names across four source directories in priority order: project-script (<project>/script/) first, then project, install, and default (bin/defaults/script/) last.
A policy.js placed in <project>/script/ therefore shadows the shipped default without modifying the installation.
|
Policy Enforcement
Wren:IDM enforces policies through three independent paths.
Router Filter
Via conf/router.json, Wren:IDM registers policyFilter.js as an onRequest filter for resource paths matching ^(managed|system|repo/internal)($|(/.+)) on the create and update methods.
Before forwarding the request, the filter calls policy/<resource path> (for example, policy/managed/user for a request targeting managed/user) with the validateObject action and the request content.
A non-true result causes the filter to throw a 403 Policy validation failed error and aborts the request.
Managed Object Patches
ManagedObjectSet enforces policies on patch requests against managed objects.
The service applies the JSON patch operations and computes the new value.
It then builds a partial object containing only the patched fields with their post-patch values.
It then issues a validateProperty action on policy/managed/<object>/<id>.
Inside conditional policy evaluation, fullObject equals the request body.
Conditional policies with dependencies on fields not included in the patch are therefore skipped, not failed, during patch-triggered validation.
The request is rejected with a 403 Failed policy validation if the result is not true.
Explicit REST Calls
REST clients may invoke policy validation directly via the /policy endpoint without persisting the resource (see REST Endpoint).
Disabling Enforcement
Both enforcement paths consult the boot property openidm.policy.enforcement.enabled, which defaults to true in conf/boot/boot.properties.
Setting the property to false disables enforcement: policyFilter.js returns immediately, and ManagedObjectSet skips the patch-time validation.
Because ManagedObjectSet reads the property once at construction time and stores it as a final field, a service restart is required for the change to take effect.
The toggle does not affect explicit calls to /policy?_action=validateObject or validateProperty.
Those always run the validation and return the result.
|
REST Endpoint
The service registers at /policy* with STARTS_WITH routing.
The path after /policy/ is treated as the resource path being validated.
The handler accepts only READ and ACTION requests.
All other request types return a NotSupportedException.
| Method | Action | Description |
|---|---|---|
|
— |
Reads the policy configuration for the resource path encoded in the URL.
+
Empty path ( |
|
|
Validates the entire request content against every property configured for the resource.
The response is |
|
|
Validates only the fields supplied in the request content against their configured policies.
The request body is a partial object with property names as keys and their values to validate as values (e.g. |
When using validateProperty with properties that have conditionalPolicies with dependencies, the caller must include all dependency-field values in the request body.
This applies even if those fields are not being validated.
A missing dependency field causes the conditional policy to be silently skipped.
The response still reports "result": true even if the conditional policy would have failed.
This same behaviour applies on patch requests through the router filter (see Managed Object Patches).
|
failedPolicyRequirements entry shape{
"property": "password",
"policyRequirements": [
{ "policyRequirement": "MIN_LENGTH", "params": { "minLength": 8 } }
]
}
The policy script receives the standard scripted-service bindings (request, context, resourceName, resourcePath) and the policy-specific resources binding injected by PolicyService (a defensive copy of configuration.resources).