Repository navigation
Conversation
2f61541 to
779494c
Compare
maximthomas
left a comment
There was a problem hiding this comment.
praise: The read side is fixed at the right place and is tested.
effectiveRoles.isNowWithinConstraintskips an invalid constraint without granting the role. With the baseeffectiveRoles.js,effectiveRolesTestfails with the exception from the issue ("The end instant must be greater the start").DateUtil.isValidIntervalcatches bothIllegalArgumentExceptionandArithmeticException, sonull, garbage and reversed intervals all returnfalseinstead of throwing.RelationshipValidator.validateRelationshiprejects the duration (:119) before it reads the referenced object.
issue (blocking): patchInstance validates the stored grant, so a PATCH that repairs an invalid duration is rejected.
openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipProvider.java:700-702, :849
patchInstance passes the old stored value (oldResource.getContent()) through convertToRepoObject before the patched one, and convertToRepoObject now calls validateTemporalConstraints. Take a grant stored with a reversed duration, which is the value #250 is about. PATCH managed/user/<id>/roles/<relId> that replaces /_refProperties/temporalConstraints/0/duration with a valid interval gets a 400: "Temporal constraint duration is not a valid ISO 8601 interval…". The admin UI edits a grant this way (RelationshipArrayView.updateRelationship → patchResourceDifferences). On the base this PATCH failed with a 500 at the getManagedObject read. With effectiveRoles fixed, this check is now the only thing stopping the edit. A PUT on the relationship, or DELETE and re-add, still works.
// RelationshipProvider.convertToRepoObject: remove
// RelationshipValidator.validateTemporalConstraints(properties);
// RelationshipProvider.patchInstance, after the `if (!modified)` return:
RelationshipValidator.validateTemporalConstraints(newValue.get(FIELD_PROPERTIES));Creates are still validated: validateRelationship (RelationshipValidator.java:119) runs on both create paths. On a direct create it runs through validateRelationshipOperand. On the managed-object path it runs through ManagedObjectSet.validateRelationshipFields.
issue (blocking): A managed-object write that carries roles/members re-validates every unchanged stored grant after the object is committed.
openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipProvider.java:503, openidm-core/src/main/java/org/forgerock/openidm/managed/CollectionRelationshipProvider.java:197-201, openidm-core/src/main/java/org/forgerock/openidm/managed/ManagedObjectSet.java:574-596
ManagedObjectSet.update validates only new or changed items before the write: validateRelationshipField skips items equal to the stored ones. It then commits the object (:590), and persistRelationships sends every item that has an _id through updateInstance, whose first statement is convertToRepoObject. Take a user holding a stored invalid grant and send PATCH managed/user/<id> with add /roles/-. The same applies to a PUT with roles, a recon that maps roles, and a conditional-role update that rewrites members (conditionalRoles.js:117-118). The response is a 400 naming a grant the request never touched. By then the user document, the clearNotIn deletions and the new grant are already persisted. On the base the same request failed with a 500 in populateVirtualProperties, before the commit. I traced this by reading the code and did not run it, which would need a server with a seeded grant.
// RelationshipProvider.updateInstance, with the call removed from convertToRepoObject:
if (!context.containsContext(ManagedObjectContext.class)) {
RelationshipValidator.validateTemporalConstraints(request.getContent().get(FIELD_PROPERTIES));
}
final JsonValue newValue = convertToRepoObject(firstResourcePath(context, request), request.getContent());The managed-object path has already validated every changed item before the commit, in validateRelationship.
suggestion (non-blocking): Neither Java call site of validateTemporalConstraints is covered by a test.
openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipValidator.java:119, openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipProvider.java:849, openidm-core/src/test/java/org/forgerock/openidm/managed/RelationshipValidatorTest.java:264
The new data-provider cases call the static method directly. Deleting both calls also drops the old "Only 1 temporal constraint" check from the write path, and openidm-core still passes, 95/95 (mvn -o -pl openidm-util,openidm-core test).
@Test(expectedExceptions = BadRequestException.class,
expectedExceptionsMessageRegExp = "Temporal constraint duration .*")
public void testValidateRelationshipRejectsInvalidDuration() throws ResourceException {
final SchemaField schemaField = mock(SchemaField.class);
when(schemaField.isReverseRelationship()).thenReturn(false);
when(schemaField.getName()).thenReturn("roles");
final CollectionRelationshipProvider relationshipProvider = new CollectionRelationshipProvider(connectionFactory,
new ResourcePath("managed/user"), schemaField, activityLogger, managedObjectSyncService);
final JsonValue grant = json(object(
field(RelationshipUtil.REFERENCE_ID, "managed/role/r1"),
field(RelationshipUtil.REFERENCE_PROPERTIES,
makeTemporalConstraints("2016-01-02T00:00:00.000Z/2016-01-01T00:00:00.000Z").getObject())));
relationshipProvider.relationshipValidator.validateRelationship(grant, new ResourcePath("managed/user/u1"),
new RootContext(), false);
}Pin: without :119, the call falls through to the unstubbed read and the message no longer matches. Whichever write-path call remains after the fix above needs its own case with the same duration.
suggestion (non-blocking): Neither validateTemporalConstraintDurations call, in roleCreate or in roleUpdate, is covered by a test.
openidm-zip/src/main/resources/bin/defaults/script/roles/conditionalRoles.js:110, :136, openidm-zip/src/test/resources/bin/defaults/script/conditionalRolesTest.js:48
conditionalRolesTest calls the exported helper only. Deleting either call leaves ScriptRunnerTest passing (Tests run: 1, Failures: 0). As a control, return; at the top of the helper fails at conditionalRolesTest.js:56.
// conditionalRolesTest.js, inside validateTemporalConstraintDurations()
[
function (role) { conditionalRoles.roleCreate(role); },
function (role) { conditionalRoles.roleUpdate({ "_id": role._id }, role); }
].forEach(function (write) {
var rejected = false;
try {
write({ "_id": "roleWithReversedConstraint", "temporalConstraints": [ { "duration": reversedDuration } ] });
} catch (e) {
if (e.code !== 400) {
throw e;
}
rejected = true;
}
if (!rejected) {
throw { "message": "A role with a reversed temporal constraint was not rejected on write" };
}
});Pin: the role is not conditional, so no openidm call is reached. Deleting :110 or :136 makes the case fail.
suggestion (non-blocking): The guard for a stored invalid duration in isNowWithinConstraint is not tested with a null element.
openidm-zip/src/main/resources/bin/defaults/script/roles/effectiveRoles.js:104
The new cases cover {}, reversed durations and unparseable ones, but no temporalConstraints: [null]. Replacing the ternary with duration = constraint.duration leaves ScriptRunnerTest passing. A null element stored before this PR would then throw in onRetrieve again.
[
{
"_id" : "role9",
"temporalConstraints" : [ null ]
},
false
],Pin: with this row added to the effectiveRolesTest table, the mutant throws a TypeError on null.duration.
suggestion (non-blocking): No test checks the warning that is logged when an invalid duration is skipped, because the new logger in testRunner.js discards every call.
openidm-zip/src/test/resources/testRunner.js:23-26, openidm-zip/src/main/resources/bin/defaults/script/roles/effectiveRoles.js:106
Deleting the logger.warn line in isNowWithinConstraint leaves ScriptRunnerTest passing. That warning is how an operator finds the stored invalid grants after the upgrade.
// effectiveRolesTest.js, inside testProcessTemporalConstraintsForRole()
var warned = [], warn = logger.warn;
logger.warn = function () { warned.push(arguments); };
try {
effectiveRoles.processTemporalConstraints({ "_id": "role5", "temporalConstraints": [ { "duration": reversedDuration } ] });
} finally {
logger.warn = warn;
}
if (warned.length !== 1) {
throw { "message": "Expected one warning for an invalid duration, got " + warned.length };
}question (non-blocking): Should roleUpdate reject an edit that leaves a stored invalid role constraint untouched?
openidm-zip/src/main/resources/bin/defaults/script/roles/conditionalRoles.js:110
roleUpdate validates the full object. A PATCH builds that object from the stored role, so PATCH managed/role/<id> that only replaces /description gets a 400 if the role was stored with a reversed duration. On the base this edit succeeded, because postUpdate skips an unchanged constraint by comparing JSON.stringify output. The same request can also fix the constraint, so the role is not stuck. This is minor if the rejection is intended, and major if stored role constraints were meant to stay editable. Either way, an upgrade note would help: how to find stored invalid durations, and which writes they now block.
if (JSON.stringify(oldRole.temporalConstraints) !== JSON.stringify(newRole.temporalConstraints)) {
validateTemporalConstraintDurations(newRole);
}suggestion (non-blocking): The guard in createJobsForConstraint has no test, because no test loads postOperation-roles.js.
openidm-zip/src/main/resources/bin/defaults/script/roles/postOperation-roles.js:250-254
testRunner.js loads no postOperation test, and nothing else evaluates the script. Reverting the guard therefore leaves every suite passing. A reversed duration would then fail a postCreate/postUpdate after the resource is stored, and nothing would report it.
Pin: add a ScriptRunnerTest module that binds resourceName/object for a role whose constraint has a reversed duration, stubs openidm.create, loads postOperation-roles.js, and asserts that loading neither throws nor creates a schedule. Without the guard, getStartOfInterval throws "The end instant must be greater the start".
suggestion (non-blocking): The admin UI checks that disable Save and Add are not covered by any test. Only the pure isValidInterval has a QUnit case.
openidm-ui/openidm-ui-admin/src/main/js/org/forgerock/openidm/ui/admin/role/util/TemporalConstraintsUtils.js:135-141, openidm-ui/openidm-ui-admin/src/main/js/org/forgerock/openidm/ui/admin/role/TemporalConstraintsFormView.js:160-172, openidm-ui/openidm-ui-admin/src/main/js/org/forgerock/openidm/ui/admin/role/EditRoleView.js:94, openidm-ui/openidm-ui-admin/src/main/js/org/forgerock/openidm/ui/admin/role/MembersDialog.js:97
EditRoleViewTest, MembersDialogTest and TemporalConstraintsFormViewTest contain no QUnit.test. No case calls isTemporalConstraintsFormValid, validate or the validationCallbacks. A mutant such as isTemporalConstraintsFormValid returning true cannot make the admin QUnit run fail.
// TemporalConstraintsUtilsTest.js, with "jquery" added to the define list as $
QUnit.test("isTemporalConstraintsFormValid", (assert) => {
const form = (end) => $("<div><div class='temporalConstraint'>"
+ "<input class='temporalConstraintStartDate' value='04/25/2016 7:00 AM'>"
+ "<input class='temporalConstraintEndDate' value='" + end + "'></div></div>");
assert.ok(TemporalConstraintsUtils.isTemporalConstraintsFormValid(form("04/30/2016 7:00 AM")), "valid end date");
assert.notOk(TemporalConstraintsUtils.isTemporalConstraintsFormValid(form("")), "empty end date");
});suggestion (non-blocking): testInvalidTemporalConstraints checks the exception type, not which guard threw it.
openidm-core/src/test/java/org/forgerock/openidm/managed/RelationshipValidatorTest.java:285
There is no expectedExceptionsMessageRegExp, so a change to any of the three messages, or to the value formatted into {0}, still passes. Deleting a branch is still caught, because each row reaches only one guard.
@Test(dataProvider = "invalidTemporalConstraints")
public void testInvalidTemporalConstraints(JsonValue refProperties, String expectedMessage) {
try {
RelationshipValidator.validateTemporalConstraints(refProperties);
fail("Expected BadRequestException");
} catch (BadRequestException e) {
assertTrue(e.getMessage().startsWith(expectedMessage), e.getMessage());
}
}Pin: add a second data-provider column with the expected start of the message: "Temporal constraint duration", "Temporal constraints must be an array.", or "Only 1 temporal constraint".
…ns and tolerate stored ones A temporal constraint whose duration is not a valid ISO 8601 interval (e.g. its end is before its start) made the effectiveRoles onRetrieve script throw, so every read and update of the user failed with 500. The admin UI produced such a value itself: an empty end date was sent as the current time. - Reject such a constraint with 400 when a role grant or a role is written - Skip, with a warning, an invalid constraint that is already stored when calculating effective roles, expired constraints and schedules - Admin UI: require both dates with the end after the start before a role or a role member with a temporal constraint can be saved Fixes OpenIdentityPlatform#250
…write changes them - Move the duration check out of RelationshipProvider.convertToRepoObject, which also ran on the stored value of a PATCH and on every unchanged relationship persisted after a managed object update. Check a created relationship in createInstance and an updated one in updateIfChanged, only if its temporal constraints changed; this no longer depends on the schema field's "validate" flag. - conditionalRoles.roleUpdate: check a role's durations only if its temporal constraints changed. - Test each call site (create, update, PATCH, validateRelationship, roleCreate/roleUpdate), null constraint elements, the warning for a skipped duration, postOperation-roles schedules and isTemporalConstraintsFormValid; assert the rejection messages.
779494c to
8652d28
Compare
|
Both blocking issues are confirmed and fixed in 8652d28; the branch is also rebased onto the current master.
A PATCH that repairs the duration succeeds, the unchanged grants persisted by Tests for the Java call sites.
Question: should
Warning for a skipped duration.
Admin UI checks. Message assertions. |
maximthomas
left a comment
There was a problem hiding this comment.
praise: Both round-1 blockers are fixed where they arose, and each call site now has a test.
RelationshipValidator.validateChangedTemporalConstraintsvalidates only the new value, and only when the constraints differ.updateIfChanged(RelationshipProvider.java:651) uses it, so a PATCH that repairs a stored reversed duration succeeds, and unchanged grants persisted after a managed-object update are left alone.conditionalRoles.roleUpdatecompares old and newtemporalConstraints.conditionalRolesTestpins both directions: a changed invalid constraint is rejected, an unchanged stored one is kept.
issue (blocking): A managed-object PATCH/PUT that re-sends a stored invalid grant without _refProperties._id commits the object, deletes the grant, and answers 400.
openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipProvider.java:394, openidm-core/src/main/java/org/forgerock/openidm/managed/CollectionRelationshipProvider.java:174-181, :207, :607-611, openidm-core/src/main/java/org/forgerock/openidm/managed/ManagedObjectSet.java:589-596
RelationshipEqualityHash ignores _id/_rev, so validateRelationshipField treats a grant re-sent without its _id as an existing grant and does not validate it. ManagedObjectSet.update then commits the object (:589) and runs persistRelationships (:596). There the item has no _id, so it is put on the create list: clearNotIn deletes the stored grant, and createInstance rejects the same stored duration (:394). Example: a user holds a pre-upgrade grant with a reversed duration, and the client sends PATCH managed/user/<id> with replace /roles (or PUT ?_fields=*,roles) carrying that grant unchanged except for the missing _id. The user document is rewritten, the grant is gone, and the client gets 400. On the base the request failed with 500 before anything was written. This contradicts the upgrade note on two points: "Writes that leave such a constraint unchanged keep working", and post-commit rejection "only when a relationship field does not have "validate": true" (roles has it). In-repo callers never send grants this way: the admin UI uses the relationship endpoints, and conditional grants carry only _grantType. External REST clients and mappings do. I traced this through the code and did not run it, since that needs a server with a seeded grant.
// CollectionRelationshipProvider.validateRelationshipField
for (JsonValue newItem : newValue) {
if (!oldReferences.contains(new RelationshipEqualityHash(newItem))) {
logger.debug("validating new relationship {} for {}: ", newItem, propertyPtr);
relationshipValidator.validateRelationship(newItem, referrerId, context, performDuplicateAssignmentCheck);
} else {
// Equal to a stored relationship but without its _id: persistRelationships deletes the stored one and
// creates this one after the managed object is committed, where createInstance checks it again
final JsonValue id = newItem.get(FIELD_ID);
if (id == null || id.isNull()) {
RelationshipValidator.validateTemporalConstraints(newItem.get(FIELD_PROPERTIES));
}
}
}Or, to make the upgrade note hold as written: hand the stored grants to setRelationshipValueForResource and give an id-less item that is RelationshipEqualityHash-equal to a stored grant that grant's _id. The item then goes through updateInstance, and validateChangedTemporalConstraints lets the unchanged constraint through.
Pin: in CollectionRelationshipProviderTest, store grant g1 with a reversed duration, then persist [g1 without _refProperties._id] on the managed-object path. The test fails if g1 is deleted before a BadRequestException.
question (non-blocking): Should the managed-object road also accept an unchanged stored constraint when another _refProperties field of the same grant changes?
openidm-core/src/main/java/org/forgerock/openidm/managed/RelationshipValidator.java:121, openidm-core/src/main/java/org/forgerock/openidm/managed/CollectionRelationshipProvider.java:607-611
Every item whose hash differs from the stored items goes to validateRelationship, and validateRelationship checks the whole _refProperties without comparing it to the stored value. A PUT/PATCH managed/user/<id> that keeps a grant's stored reversed duration but changes _grantType or a custom refProperty therefore gets a 400 that names a duration the request did not change. The check runs before the commit, so nothing is written. The same edit through PATCH managed/user/<id>/roles/<grantId> succeeds. A probe on validateRelationshipField reproduces the 400, and deleting :121 removes it. testUpdateKeepsUnchangedInvalidTemporalConstraint enters at updateInstance, after this gate, so it does not cover it. If the upgrade note is meant to cover this road, this is a bug: match the item to the stored one by _refProperties._id and use validateChangedTemporalConstraints. Otherwise, narrow the note to the relationship endpoints.
issue (non-blocking): createJobsForConstraint reads constraint.duration without checking for a null constraint.
openidm-zip/src/main/resources/bin/defaults/script/roles/postOperation-roles.js:250, :346-347
hasConstraints accepts [null], so a null entry throws a TypeError at the new guard. effectiveRoles.js and temporalConstraints.js both check for null before reading the duration. Only a [null] stored before the upgrade can reach this, because the Java check now rejects new ones. The base threw on the same line. Not run: I did not settle whether the user's postUpdate receives the roles at all, since roles is returnByDefault: false.
if (constraint === null || constraint === undefined || !dateUtil.isValidInterval(constraint.duration)) {Pin: a postOperationRolesTest case with temporalConstraints: [null] on a changed grant.
question (non-blocking): Is it intended that the admin UI refuses to save any edit of a role whose stored constraint is reversed?
openidm-ui/openidm-ui-admin/src/main/js/org/forgerock/openidm/ui/admin/role/EditRoleView.js:105, :113-114, :119
The form loads the stored start and end dates, and areTemporalConstraintsValid then fails, so Save is disabled and save() returns early, even for a description edit. Over REST, roleUpdate accepts the unchanged constraint. This is a UI-only block if convertToIntervalString rebuilds the stored string exactly; otherwise the server would reject the save anyway, as the code comment says. Not run: a convertFromIntervalString → convertToIntervalString round trip on a duration that the pre-fix UI produced would settle which case applies. If it is intended, the upgrade note could say that such a role must be repaired before it can be edited in the admin UI.
… managed object path - CollectionRelationshipProvider.validateRelationshipField: check a relationship that a managed object write updates (matched by _refProperties._id) only if its temporal constraints changed, and check one re-sent equal to a stored relationship but without its _id before the commit, since persisting it deletes the stored relationship and creates it again. - postOperation-roles.createJobsForConstraint: skip a null constraint. - Test validateRelationshipField for a grant that keeps, changes or re-sends a stored invalid constraint, and postOperation-roles for null constraints on a role and on a created or updated grant.
|
Round 2 is addressed in 42daf14. Id-less re-sent grant is deleted and answered with 400 after the commit. Confirmed, and fixed with your first variant: Question: unchanged stored constraint while another
Question: admin UI refuses to save a role whose stored constraint is reversed. Intended. For the values from #250 the server would reject the save as well: the form keeps whole minutes ( Each new Java and JS case fails with the mutant that removes its check (or that validates the unchanged constraint again); the full |
Fixes #250
Problem
effectiveRolesis a virtual property returned by default, so itsonRetrievescript runs on every read and every update of a user. The script parses each temporal constraint duration with Joda'sInterval.parse, which throws on an interval whose end is before its start. Nothing validated the duration when a grant or a role was written, so once such a value was stored the user could neither be read nor fixed throughmanaged/user(500effectiveRoles onRetrieve script encountered exception).The admin UI produced such a value itself: with a start date in the future and an empty end date,
convertToIntervalStringsent the current time as the end. Replaying that exact request on a build without the fix:POST managed/role/<id>/membersreturns 500 after the grant is already stored, and from then onGET managed/user/<id>and even the user's grant list return 500.Changes
Reject on write (400 instead of storing the value)
DateUtil.isValidInterval(String): true only ifInterval.parseaccepts the string (null, garbage and reversed intervals are invalid;datetime/periodforms stay valid).RelationshipValidator.validateTemporalConstraints: grant constraints must be an array of at most one constraint with a validduration. It runs:validateRelationship, before the managed object is written and before virtual properties are computed (fields with"validate": true, as in the defaultmanaged.json). For a grant that a managed object write updates (matched to the stored grant by_refProperties._id), only if its temporal constraints changed. A grant re-sent equal to a stored one but without its_refProperties._idis checked like a new grant, since persisting it deletes the stored grant and creates it again;RelationshipProvider.createInstancefor every created grant, whatever thevalidateflag;RelationshipProvider.updateIfChanged(PUT and PATCH of a grant, and the grants persisted by a managed object update) only if the update changes the grant's temporal constraints.It replaces the "Only 1 temporal constraint" check in
convertToRepoObject, which also ran on the stored value of a PATCH and on every unchanged grant persisted after a managed object update.conditionalRoles.roleCreate, androleUpdateif the update changes the role's temporal constraints: the same check for a role's own temporal constraints.Tolerate values that are already stored
effectiveRoles.processConstraintsandtemporalConstraints.areConstraintsExpiredskip an invalid constraint with a warning; it never grants the role.postOperation-roles.createJobsForConstraintlogs and creates no schedules for an invalid duration or anullconstraint instead of failing a request whose resource is already stored.PATCH managed/user/<id>/roles/<grantId>replacing the duration) succeeds.Admin UI
TemporalConstraintsUtils.isValidIntervalrequires both dates and the end after the start;TemporalConstraintsFormViewshows an error under the end date, andEditRoleView(Save) andMembersDialog(Add) stay disabled while the form is invalid.EditRoleView.savealso refuses to send an invalid form.Upgrade note
Grants and roles stored before this change with an invalid temporal constraint duration (e.g. an end before the start) are left as they are. They no longer break reading the user and never grant the role; whenever the effective roles of such a user are calculated, the log shows
Ignoring temporal constraint with an invalid duration <duration>. To find them, look for that warning, or list the grants withGET managed/user/<id>/roles?_queryFilter=true&_fields=_ref,_refPropertiesand the roles withGET managed/role?_queryFilter=true&_fields=temporalConstraints.Writes that leave such a constraint unchanged keep working, whether through the relationship endpoint or through the managed object, including writes that change other
_refPropertiesof the grant. A write that changes it must make it valid, otherwise it is rejected with 400. Repair it by replacing the duration, e.g.PATCH managed/user/<id>/roles/<grantId>withreplace /_refProperties/temporalConstraints/0/duration, or remove the constraint or the grant.A managed object write that re-sends such a grant without its
_refProperties._idis rejected with 400 before anything is written: persisting a grant without its_iddeletes the stored grant and creates it again, so it is checked like a new grant. Send the grants with the_refProperties._idthat a read of the managed object returns.The admin UI does not save a role whose stored temporal constraint is invalid, not even an edit of another field: the form shows the error under the end date. Correct the dates in the form, or turn the temporal constraint off, to save the role. The server would reject most of these saves anyway, since the form keeps whole minutes and the end that the admin UI used to store carried seconds, so the rebuilt duration differs from the stored one.
Only when a relationship field does not have
"validate": true(the defaultmanaged.jsonsets it on every relationship field), a managed object write that adds a grant with an invalid duration, or re-sends one without its_id, is rejected after the managed object itself has been written, since the check then runs when the grant is persisted.Testing
DateUtilTest;RelationshipValidatorTest(valid / invalid_refPropertieswith the expected message, changed / unchanged constraints,validateRelationship);CollectionRelationshipProviderTeston the managed object path: creating an invalid grant, updating a grant with a changed invalid / unchanged stored invalid constraint, PATCH that repairs / breaks the duration;validateRelationshipFieldwith a grant that keeps a stored invalid constraint while another field changes, that changes it to an invalid one, and that is re-sent with / without its_id. Allopenidm-util,openidm-coreandopenidm-ziptests pass.ScriptRunnerTest):effectiveRolesTest,temporalConstraintsTest,conditionalRolesTestcover reversed, unparseable andnullconstraints, the warning for a skipped duration, androleCreate/roleUpdate(rejecting a new or changed invalid constraint, keeping an unchanged stored one); the newpostOperationRolesTestchecks that an invalid duration or anullconstraint on a role or a created / updated grant creates no schedule and does not fail.testRunner.jsprovides a no-oplogger, as the scripts log through the binding OpenIDM supplies at runtime.convertToRepoObjector makesroleUpdatevalidate unconditionally); every mutant fails the suite.isValidInterval,isTemporalConstraintsFormValid(129 tests, 0 failed); eslint clean on the changed UI files.