Skip to content

effectiveRole calculation crashes if a mistake was done with a role assigned using time constraint #250

Description

@lombao

Describe the bug
If for wahtever reason you manage to assign a Role to a user using time constraint. and the start time is more recent than the end time a crash happen that cannot be solved easily as internal error happens just accesing the use

To Reproduce
Assign a role with the endtime behind starttime, for instance, starttime today , and endtime yesterday.

Expected behavior
Any error, or warning, but not an 500 Internal Error
like this

"Oct 06, 2026 12:02:59 PM org.forgerock.openidm.servlet.internal.ServletConnectionFactory$3 handleException
WARNING: Resource exception: 500 Internal Server Error: "effectiveRoles onRetrieve script encountered exception"
org.forgerock.json.resource.InternalServerErrorException: effectiveRoles onRetrieve script encountered exception
"

Screenshots
The thing is that the default onRetrioeve script for effectiveRoles triggers this script require('roles/effectiveRoles').calculateEffectiveRoles(object, 'roles');"
"effectiveRoles" : {
"type" : "array",
"title" : "Effective Roles",
"viewable" : false,
"returnByDefault" : true,
"isVirtual" : true,
"onRetrieve" : {
"type" : "text/javascript",
"source" : "require('roles/effectiveRoles').calculateEffectiveRoles(object, 'roles');"
},
"items" : {
"type" : "object"
}
},

And then, in the script , in line 83 we have
if (org.forgerock.openidm.util.DateUtil.getDateUtil().isNowWithinInterval(constraint.duration)) {
return true;
}

That isNowWithinInternal crashes if the data is unexpected, I think that should be replaced by something more fault tolerant.

Vote to raise the priority 🖐️

Activity

  1. lombao commented on Oct 6, 2026

    @lombao
    Author

    Just to illustrate what I am saying, I added this piece of code in my effectiveRoles to sort my own situation, in case this is of any help

    ``
    function isNowWithinInterval(interval) {
    const [startString, endString] = interval.split('/');

       const start = new Date(startString);
       const end = new Date(endString);
       const now = new Date();
    
     // Invalid dates
     if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
       logger.warn(`WARNING: Invalid time interval: ${interval}`);
       return false;
     }
    
    // No start: valid until the end
    if (!start) {
      return now <= end;
    }
    
    // No start: valid until the end
    if (!start) {
      return now <= end;
    }
    
    // Invalid interval: end is before start
    if (end < start) {
      logger.warn(`Invalid interval: end is before start: ${interval}`);
      return false;
    }
    
    return now >= start && now <= end;
    

    }
    `

  2. vharseko commented on Oct 6, 2026

    @vharseko
    Member

    Thanks for the report and for sharing your workaround, it pinpoints the problem: DateUtil.isNowWithinInterval parses the duration with Joda's Interval.parse, which throws on an interval whose end is before its start. Since effectiveRoles is returnByDefault, that script runs on every read and every update of the user, so the user can neither be read nor fixed through managed/user.

    A note on the snippet: new Date(...) only understands the datetime/datetime form, but a temporal constraint may also be written as datetime/period or period/datetime (e.g. 2026-10-01T00:00:00Z/P30D). With that replacement such valid constraints silently stop granting the role. (Also, !start is never true for a Date object, so those two blocks never run.)

    A workaround that keeps the existing interval semantics is to guard the existing call rather than re-parse the dates:

    1. Copy bin/defaults/script/roles/effectiveRoles.js to <your project>/script/roles/effectiveRoles.js — the project script directory takes precedence over bin/defaults/script, so the default file stays untouched.
    2. In the copy, replace processConstraints with:
        function processConstraints(object) {
            if (object.temporalConstraints !== undefined && object.temporalConstraints.length) {
                // Loops through constraints
                for (var index in object.temporalConstraints) {
                    var constraint = object.temporalConstraints[index];
                    try {
                        // If at least one constraint passes, the role is in effect
                        if (org.forgerock.openidm.util.DateUtil.getDateUtil().isNowWithinInterval(constraint.duration)) {
                            return true;
                        }
                    } catch (e) {
                        // An invalid duration (e.g. end before start) neither breaks the read nor grants the role
                        logger.warn("Ignoring temporal constraint with an invalid duration {}", String(constraint.duration));
                    }
                }
                return false;
            }
            // No temporal constraints
            return true;
        };

    With that in place the user can be read again, and the broken constraint can be corrected or removed from the admin UI or over REST, e.g. list the grants with GET /openidm/managed/user/<userId>/roles?_queryFilter=true&_fields=_ref,_refProperties and remove the bad one with DELETE /openidm/managed/user/<userId>/roles/<grantId>. If the reversed interval is on the role itself (temporalConstraints of managed/role/<roleId>), the same workaround applies; fix the role afterwards.

    We are preparing a fix that:

    • rejects such a temporal constraint with 400 Bad Request when a role grant or a role is written, instead of storing it;
    • makes effectiveRoles and the other temporal-constraint scripts skip, with a warning, an invalid constraint that is already stored, so it does not grant the role and no longer breaks reading the user.
  3. lombao commented on Oct 6, 2026

    @lombao
    Author

    makes completely sense.. many thx

  4. vharseko commented on Oct 6, 2026

    @vharseko
    Member

    Two corrections to the workaround above, after trying it on a build without the fix:

    • Restart OpenIDM after copying the script. Modules loaded with require() are cached, so until the restart the server keeps running the default bin/defaults/script/roles/effectiveRoles.js and the user still fails with 500.

    • The grant id is _refProperties._id, not a top-level _id. For example:

      GET /openidm/managed/user/<userId>/roles?_queryFilter=true&_fields=_ref,_refProperties
      → {"result":[{"_ref":"managed/role/<roleId>","_refProperties":{"temporalConstraints":[...],"_id":"<grantId>", ...}}]}
      
      DELETE /openidm/managed/user/<userId>/roles/<grantId>
      

    With the restart, reading the user returned 200 again (the broken grant did not count towards effectiveRoles), and the DELETE removed the grant.

    How such a value can come from the admin UI: if the start date of a temporal constraint is set and the end date is left empty, the UI fills in the current time as the end. With a start date in the future that is an interval whose end is before its start. This happens both on the role's own temporal constraint and when adding role members with a temporal constraint.

  5. added
    bugSomething isn't working
    javaPull requests that update Java code
    javascriptPull requests that update Javascript code
    uiAdmin and end-user web UI (openidm-ui-*)
    on Oct 6, 2026
  6. added 4 commits that reference this issue on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingjavaPull requests that update Java codejavascriptPull requests that update Javascript codeuiAdmin and end-user web UI (openidm-ui-*)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions