Rules & Actions API
Introduction
We strongly suggest studying the official Groovy language documentation before using those APIs, and use it as reference:
http://docs.groovy-lang.org/latest/html/documentation/
General Overview
Rule Types & Categories
There are several rule types supported by Memority, that are listed in the table below:
Type | Signature | Description |
---|---|---|
CHOICES |
| Return a set of choices to choose from. Typically used to populate a select box in the UI. The |
COMPUTE |
| Compute a value from the context. The value can be anything (it is up to the caller to safely cast the returned value) |
CONDITION |
| Decide whether or not a condition is fulfilled (according to the given context), returning a true or false result. |
NORMALIZE |
| Normalize a given value. The context may be used to perform the normalization, but it is neither mandatory nor common. |
SELECT |
| Select a value amongst a list of choices. |
TRANSFORM |
| Modify the Context, applying a direct modification. Note that modifications are limited to certain values only. There is no return type. |
VALIDATION |
| Validate the given value, optionally using the context to do so. The |
ACTION |
| Custom code to attach to business policies, features and workflows |
LIB |
| Methods and classes meant to be used by other rules of any type (including Libraries), but of the same or compatible category |
Rules are also considered usable for a certain category of usage, which dictates what information is available to the Rule upon execution.
These categories are:
ACCESS_CODE_POLICY
: The rule is executed in the context of an access code policy executionATTRIBUTE
: The rule is executed in the context of a single attribute, whose properties and value are made availableBUSINESS_POLICY
: The rule is executed in the context of a triggerDIMENSION
: The dimension rule (normalization, validation...) is executed in the context of an assignationFEATURE
: The Rule is executed in the context of an feature operationFIELD
: The rule is executed in the context of a Field (choices, validation...)LIST_WIDGET
: The rule is executed in the context of a List Widget (compute)MYMFA
: The rule is executed in the context of an MyMFA enrollmentOBJECT
: The rule is executed in the context of an IM object that is being manipulatedOBJECT_LIFECYCLE_POLICY
: The rule is executed in the context of an object lifecycle policyOBJECT_POLICY
: The rule is executed in the context of a object policy triggerOBJECT_RECERTIFICATION
: The rule is executed in the context of an Object RecertificationREPORTING
: The rule is executed in the context of a reportingROLE
: This rule is executed in the context of a Role AssignmentROLE_DIMENSION
: Right/Role dimension rule (normalization, validation, ...) executed in the context of a binding configurationROLE_REQUEST
: This rule is executed in the context of a Role Request PolicySCOPE
: The rule is an inline groovy snippet in a scopeWORKFLOW
: This rule is a BPMN ScriptRule script
Scopes
Variable parts in scope search expressions are evaluated as compute rules of category SCOPE
.
Input & Output
Script Rules must comply with the Rule Type signature, most notably concerning their return type. The script content is compiled and checked when saved. The verification is limited but make sure that everything in the script is type safe and, when possible, verify that the return type is conform with the rule type signature.
The script input is provided as variables made directly available to the script with naming conventions that are detailed later in this document.
Sandboxing
To ensure maximum safety, script rules are executed in a sandboxed or controlled environment that:
checks which classes and methods the script uses against a white list of authorized APIs
checks that the script execution is below a certain threshold:
500ms for synchronous rules
1 minute for asynchronous rules
all Synchronzation service rules
object/business policies
rules that are always executed as part of a quartz job, eg. Grace Period actions
Supported Scripting Languages
For now only Groovy Scripts are supported.
Scripting Guidelines
The following points must be taken into considerations when coding rules:
Rules should never have any side effects (except actions).
Rules must be executed quickly
Rules must be safe and test for variable nullity and such
Rules must only use the API that has been declared as usable (see below)
Bindings
Scripts access external values through bindings that are injected upon execution. Those bindings depends on the Type and Category of the Rule. They are detailed below:
Binding Variable | Java Type | Description | Availability | Example Usage | |
---|---|---|---|---|---|
Rules | Actions | ||||
CTXT |
| Rule Context provided to the script. This is generic as it mainly composes other contexts. It can serve as a single point of entry from which other contexts are obtained. |
|
|
|
VARS |
| A Context meant to hold variables, that are mutable. Typically used in context transformation rules. |
|
|
|
ACTORS |
| A Context used to hold information about the currently logged user (the subject), the requester (typically the front-end user), and later the workflow approvers. |
|
|
|
SUBJECT |
| The authenticated user. Same as |
|
| |
REQUESTER |
| The impersonated user. Same as |
|
| |
RULE |
| Provides information about the rule being executed |
|
|
|
OPERATION |
| A Context used to access the properties and attributes of the object we are operating on. |
|
|
|
OBJECT |
| Same as The |
|
| Attributes can be accessed as properties:
In a mutable context, attributes can be modified as properties as well:
|
ATTRIBUTE |
| A Context used to access the properties of the attribute we are operating on. |
|
|
|
TRIGGER |
| A Context used to access the properties and state of the trigger we are operating on. |
|
|
|
FEATURE |
| A Context used to access the properties and state of the feature we are operating on.
|
|
|
|
WORKFLOW |
| A Context used to access the properties and state of the current workflow |
|
|
|
API_WORKFLOW |
| An API to manage workflows executions |
|
|
|
EXTERNAL |
| A Context used to access the external correlation context passed in the request, in the following cases:
|
|
|
|
DIMENSION |
| Context information for Rule-based right/role dimension mapping |
|
|
|
LOG |
| Provides logging capabilities. |
|
|
|
REF |
| A service that provides access to reference table data. |
|
|
|
SEQ |
| A service that provides access to sequences (IDM only) |
|
|
|
FIND |
| A service that provides access to a finding API for other objects |
|
|
|
MANAGE |
| A service that provides access to a persistence (creation, patching and deletion) of managed objects. |
|
|
|
CHANGES |
| A service that provides evaluation of attributes changes applied between |
|
|
|
NOTIFY |
| A service that let one create and send notifications. |
|
|
|
VALUE |
| Value to validate / normalize. Only available for Rule Types whose signature takes a value parameter as an argument. |
|
|
|
CHOICES |
| List of possible choices. Only available to the Select Rule Type. |
|
| |
ACCESS_CODE |
| A service that provides methods for access code management (creation, send notifications, use) |
|
|
|
API_FEATURE |
| A service that provides methods to perform some operations on Features and Workflows |
|
|
|
API_MYMFA |
| A service that provides methods for managing myMfa accounts. This service extends the |
|
|
CODE
|
SETTINGS | Custom Settings retrieval API (IDM, BUM, SYNC) | A service that enables to read custom Setting values. There are caveats:
The default
If a Setting does not exist, an |
|
|
|
ROLE_REQUEST | A Context used to access the properties of the role request we are operating on.
|
|
|
| |
ROLE_ASSIGNMENT |
| A Context used to access the properties of the role assignment we are operating on.
|
|
|
|
LIST_WIDGET |
| A Context used to access the properties required to compute the list content and evaluate the display conditions
|
|
|
|
REPORTING |
| A context used to access properties related to a reporting execution. Contains the user request. |
|
|
|
REPORTING_DOCUMENT |
| Represent the current reporting document when applicable (for instance in display conditions of Reporting List Widget) |
| ||
API_REPORTING |
| API for saving documents and reading filters from reporting collections (See Reporting Service Specification) |
|
|
CODE
|
API_RECERTIFICATION |
| Provides a method to estimate the next recertification date of a Role Assignment |
|
|
CODE
|
API_RECERTIFICATION_CAMPAIGN |
| BUM API to launch recertification campaigns |
|
|
CODE
|
API_OTP |
| API to synchronize authoritative OTP addresses |
|
|
GROOVY
|
PASSWORD |
| API to generate random passwords |
GROOVY
| ||
API_ROLE_ASSIGNMENT |
| API for managing Role Assignments |
|
GROOVY
|
Contexts Availability
VARS
and RULE
contexts are always available.
App | Rule | Type | Use Case | Category | Context Availability | Comments | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SUBJECT | OBJECT | ATTRIBUTE | TRIGGER | FEATURE | EXTERNAL | DIMENSION | LIST_WIDGET | REPORTING | ROLE_ASSIGNMENT | ||||||
IDM | Object Policy Action | ACTION | Event-based object policy execution | OBJECT_POLICY |
|
* |
|
| |||||||
Scheduled object policy execution | OBJECT_POLICY |
|
| ||||||||||||
Object Lifecycle Policy Rule | TRANSFORM | <all> | OBJECT_LIFECYCLE_POLICY |
* |
| ||||||||||
Object Validation | VALIDATION | <all> | OBJECT |
* |
| ||||||||||
Attribute Validation | VALIDATION | <all> | OBJECT |
* |
| ||||||||||
Attribute Choices | CHOICES | Object processing | OBJECT |
* |
| The rule may return less choices when performing object validation, ie when category is OBJECT. When building the search contract, the rule must return all possible choices. | |||||||||
Search contract construction | ATTRIBUTE |
| |||||||||||||
Attribute Normalization | NORMALIZE | <all> | OBJECT |
* |
|
| |||||||||
Attribute Initialization | COMPUTE | <all> | OBJECT |
* |
|
| |||||||||
Attribute Computation | COMPUTE | <all> | OBJECT |
* |
|
| |||||||||
Attribute Resolution | COMPUTE | <all> | OBJECT |
* |
| ||||||||||
Reference Attribute Scope Validation | COMPUTE | <all> | SCOPE |
* |
| ||||||||||
"Enabled" Attribute Transition Validation | CONDITION | <all> | OBJECT |
|
* | ||||||||||
ID Generation Rule | COMPUTE | <all> | OBJECT |
|
* | ||||||||||
Deduplication Policy Scope | COMPUTE | <all> | SCOPE |
* | |||||||||||
Deduplication Policy Assignment Scope | COMPUTE | <all> | SCOPE |
* | |||||||||||
Password Policy Assignment Scope | COMPUTE | <all> | SCOPE |
** |
| ||||||||||
Dimension Initialization | COMPUTE | <all> | DIMENSION |
|
|
| |||||||||
Dimension Normalization | NORMALIZE | <all> | DIMENSION |
|
| ||||||||||
Dimension Validation | VALIDATION | <all> | DIMENSION |
|
| ||||||||||
Dimension Choice | CHOICES | <all> | DIMENSION |
|
| ||||||||||
Role Type Rules for RAs | - | <all> | DIMENSION |
|
| ||||||||||
Dimension Mapping | COMPUTE | <all> | DIMENSION |
|
|
| |||||||||
BUM | Feature Action | ACTION | Operation execution | FEATURE |
|
* |
| ||||||||
Scheduled operation | FEATURE |
|
* |
| |||||||||||
Feature Scope | COMPUTE | <all> | SCOPE |
| |||||||||||
Feature Initialization | COMPUTE | <all> | OBJECT |
|
| ||||||||||
Access Code Policy Action | ACTION | <all> | ACCESS_CODE_POLICY |
|
|
|
| ||||||||
Business Policy | ACTION | Event-based business policy execution | BUSINESS_POLICY |
|
* |
|
| ||||||||
Scheduled business policy execution | BUSINESS_POLICY |
|
| ||||||||||||
Right or Role Dimension Mapping | COMPUTE | <all> | ROLE |
** |
| ||||||||||
Role Assignment Manual Provisioning Policy | ACTION | <all> | MANUAL_PROVISIONING_POLICY |
** | |||||||||||
List Widget Content Rule | COMPUTE | <all> | LIST_WIDGET |
|
** |
|
For display condition, | ||||||||
Reporting widget dynamic fixed value | COMPUTE | <all> | REPORTING_WIDGET |
| |||||||||||
Attribute Display Condition | CONDITION | <all> | OBJECT |
|
** | ||||||||||
RoleAssignment Field Display Condition | CONDITION | Role Assignment Field Widgets | FIELD |
|
|
| |||||||||
RoleAssignment Field Validation | VALIDATION | Role Assignment Field Widgets | FIELD |
|
|
| |||||||||
RoleAssignment Field Choice | CHOICES | Role Assignment Field Widgets | FIELD |
|
|
|
* Original object available only for PATCH
operation
** Original object never available
Implementing Groovy Rules
The Groovy scripting language is very close to Java. One can import and use Java classes directly and some Java classes are even automatically imported in all groovy scripts. For more information on the features and language, see the official groovy language website at http://groovy-lang.org/
Memority uses Groovy version 2.5.
Imports
Memority does automatically import a certain number of packages. Importing them explicitly does not causes runtime error, so this can be done also to ensure the script compiles or to simplify auto-completion in an IDE.
The automatically imported packages are the following (imported as star imports: import name.of.my.package.*):
java.time
com.memority.toolkit.core.api
com.memority.toolkit.core.api.misc
com.memority.toolkit.core.api.util
com.memority.toolkit.rule.api
com.memority.toolkit.rule.api.context
com.memority.citadel.shared.api
com.memority.citadel.shared.api.context
com.memority.citadel.shared.api.im
com.memority.citadel.shared.api.im.operation
com.memority.citadel.shared.api.services
com.memority.citadel.shared.api.services.finder
com.memority.citadel.shared.api.services.reftable
com.memority.citadel.shared.api.services.seq
com.memority.citadel.shared.api.services.notification
com.memority.citadel.shared.api.services.accesscode
com.memority.citadel.shared.api.services.feature
com.memority.citadel.shared.api.services.myMfa
Strict Typing
Groovy Rules are checked for safety, but this check can only be performed if types are strictly enforced. For this reason, all variables should be explicitly typed. This does not apply to bound variables (anymore).
final firstName = OBJECT.firstName as String
final lastName = OBJECT.lastName as String
return lastName + ", " + firstName
Declared Functions
You can declare groovy closures and functions as follows. Because of security constraints, arguments must be typed.
final shout = {
String msg1, String msg2 ->
final c = msg1.toUpperCase() + " " + msg2.toUpperCase()
LOG.warn(c)
}
shout("foo", "bar") //logs "FOO BAR"
int max(int a, int b){( a < b )? b : a}
max(15,25) //returns 25
Java API
Groovy Script Rules can access all of the API that is made available by Memority in the citadel-shared-api
artefact. This correspond to the com.memority.citadel.shared.api
package and its subpackages. Please refer to the javadoc for more information at this stage.
Script Rules can also access a subset of the Java API, as detailed below:
Each line corresponds to a regular expression that matches authorized APIs (classes and methods).
com\\.memority\\.toolkit\\.core\\.api\\..*,\
com\\.memority\\.toolkit\\.rule\\.api\\..*,\
java\\.lang\\.Boolean.*,\
java\\.lang\\.Byte.*,\
java\\.lang\\.Character.*,\
java\\.lang\\.Double.*,\
java\\.lang\\.Enum.*,\
java\\.lang\\.Float.*,\
java\\.lang\\.Integer.*,\
java\\.lang\\.Long.*,\
java\\.lang\\.Math.*,\
java\\.lang\\.Number.*,\
java\\.lang\\.Object.*,\
java\\.lang\\.Short.*,\
java\\.lang\\.String.*,\
java\\.time\\..*,\
java\\.util\\.Base64.*,\
java\\.util\\.Date.*,\
java\\.util\\.Locale.*,\
java\\.util\\.Random.*,\
java\\.util\\.UUID.*,\
java\\.util\\..*Map.*,\
java\\.util\\..*List.*,\
org\\.codehaus\\.groovy\\.runtime\\.DateGroovyMethods#.*,\
org\\.codehaus\\.groovy\\.runtime\\.EncodingGroovyMethods#.*,\
org\\.codehaus\\.groovy\\.runtime\\.StringGroovyMethods#.*,\
org\\.codehaus\\.groovy\\.runtime\\.DefaultGroovyMethods#.*,\
org\\.codehaus\\.groovy\\.runtime\\.DefaultGroovyStaticMethods#.*
Examples
Choice Rule
if (OPERATION.operation == ObjectOperation.CREATE) {
return ChoicesRuleResult.of('some_i18n_prefix', ['foo', 'bar'])
} else {
return ChoicesRuleResult.of('some_i18n_prefix', REF['myChoiceMode']?.list("code"))
}
Compute Rule
if(OPERATION.operation != ObjectOperation.CREATE){
if(OPERATION.operation == ObjectOperation.PATCH){
def originalFirstName = OPERATION.getOriginalAttributes().firstName
def originalLastName = OPERATION.getOriginalAttributes().lastName
def firstName = OBJECT.firstName
def lastName = OBJECT.lastName
if(originalFirstName==firstName && originalLastName==lastName){
LOG.debug("Login compute rule - No computation since no name modification")
return OBJECT.login
}
}
}
Normalize Rule
'_' + (VALUE as String) + '_'
Validation Rule
import com.memority.citadel.shared.api.im.ApiObject
import java.time.Instant
/*
This validation rule is used to check activationFrom attribute:
- activationFrom is after or equals today (option)
*/
// Check if we allow enabledFrom after or equals today
def allowActivationFromBeforeToday = SETTINGS.get("idm.tenant.attribute.activationFrom.allowActivationFromBeforeToday") as boolean
if (!allowActivationFromBeforeToday) {
if (OBJECT.activationFrom?.isBefore(Instant.now())) {
return ValidationRuleResult.invalid("Attribute activationFrom is before now", "ui.errors.attribute.activationFrom.beforeNow.msg")
}
}
return ValidationRuleResult.valid()
Library Rule
Library
// public method
int foo(FooBar fooBar) {
switch(fooBar) {
case FOO:
return 42
default:
return 666
}
}
enum FooBar {
FOO, BAR
}
// Libraries may optionnaly perform some initialization in the script body and return a LibraryInitializationOutcome
// returning nothing or null is interpreted as a success, returning a failure will throw an exception at call site, aborting the calling rule
return InitializationOutcome.success()
Usage in another rule
import com.memority.toolkit.rule.api.groovy.transform.Lib
import groovy.transform.Field
// Library rules have "magic" names, starting with package _libs_.<rule id>
// Where <rule id> is the rule primary key with dashes (-) replaced with dollars ($)
// Main lib class
import _libs_.test$lib.Main as SomeLib
// FooBar enum
import _libs_.test$lib.FooBar
@Lib // Library injection annotation
@Field // Make it a field (not just a variable) to make it accessible inside functions
private final SomeLib someLib
int doIt(FooBar fooBar) {
return someLib.foo(fooBar)
}
return doIt(FooBar.FOO) // should return 42
IDM API
Sequences
All scripts that execute on the IDM can access a set of sequences that can be queried and incremented. Those sequences are backed by an SQL sequence in database.
Memority provides 10 sequences, builtin, that are accessed by index. The SequenceProvider
is injected in the Rule's API as the SEQ
variable and provides access to those sequences.
SequenceProvider
/**
* A Sequence Provider provides access to the sequences that are managed by the IDM.
*/
public interface SequenceProvider {
int MIN_SEQ = 0;
int MAX_SEQ = 9;
/**
* Return the {@link Sequence} for the given index. The index must be between {@value MIN_SEQ} and
* {@value MAX_SEQ}.
*
* @param index of sequence
* @return the requested Sequence
* @throws IllegalArgumentException if the sequence index is not in the required range
*/
Sequence get(int index);
}
Sequence
/**
* A Sequence is a stateful store for a Long value, that can be queried and incremented.
*/
public interface Sequence {
/**
* @return current value of the sequence
*/
long get();
/**
* @return return the sequence value and increment it
*/
long getAndIncrement();
/**
* @param value value to reset to
*/
void reset(int value);
}
Example Script
import com.memority.citadel.shared.api.seq.SequenceProvider
final def seq = SEQ as SequenceProvider
return seq.get(0).incrementAndGet();
Object Finders
All scripts can access a Finder API that can be used to lookup objects. Depending on where the script is executed, a REST call may be performed on the IDM service. Beware that a script using this API may (whether locally or remotely through REST) may be very slow to execute. The script itself should check the context and not make the call unless absolutely necessary.
By default, when no specific projection is defined, only the attributes marked as Excerpt will be sent.
Search expression DSL
All scripts can easily build search expressions using the auto-imported expr
groovy helper, which exposes SearchExpressions
' static methods to a closure:
def someExpr = expr{prop('lastName').eq('Doe').and(...)}
//closures
def propName = 'theProp'
def propVal = 'thePropVal'
def someExpr2 = expr{ prop(propName).eq(propVal).and(...) }
//example without "prop" and with functions
def someExpr3 = expr { enabled.eq(true) & hasRoleMatching(role.eq('Admin)) }
def emptyExpr = expr{}
ObjectFinderProvider
/**
* Provide ObjectFinder for different ObjectKind
*/
public interface ObjectFinderProvider {
/**
*
* @return ObjectFinder for Identity type
*/
default ObjectFinder identity(){
return kind(ObjectKind.IDENTITY);
};
/**
*
* @return ObjectFinder for Organisation type
*/
default ObjectFinder organization(){
return kind(ObjectKind.ORGANIZATION);
};
/**
*
* @return ObjectFinder for Resources Type
*/
default ObjectFinder resource(){
return kind(ObjectKind.RESOURCE);
};
/**
*
* @param kind object Kind
* @return ObjectFinder for a given type
*/
ObjectFinder kind(ObjectKind kind) ;
}
ObjectFinder
/**
* An object Finder is an object that give us specific service for quering entity
*/
public interface ObjectFinder {
/**
* Return all objects that match the given search expression.
*
* @param expr the search expression
* @return the list of matching objects, never <code>null</code>
*/
List<ApiObject> allMatching(SearchExpression expr);
/**
* Return the first object that matches the given search expression.
*
* @param expr the search expression
* @return the first object that matches, or <code>null</code> if none match
*/
ApiObject matching(SearchExpression expr);
/**
* Return the object with the given id
*
* @param id the object id
* @return the object, or <code>null</code> if it does not exist
*/
ApiObject withId(String id);
}
Example Script
// Script Example with no projection specified, in this case only attributes marked as excerpt on the concerned object type are returned
managerIdentity = FIND.identity().withId(manager)
return managerIdentity?.email
// Script Example using withAttributesProjection method that allows to specify which attributes to retrieve
managerIdentity = FIND.identity().withAttributesProjection("commonName").withId(manager)
return managerIdentity?.commonName
// Script Example using withExcerptProjection method that allows to retrieve only attributes marked as excerpt on the concerned object type
managerIdentity = FIND.identity().withExcerptProjection().withId(manager)
return managerIdentity?.email
// Script Example using withExcerptProjection method that allows to retrieve only attributes marked as qalifying on the concerned object type
managerIdentity = FIND.identity().withQualifyingProjection().withId(manager)
return managerIdentity?.email
Object Manager
The object manager offers APIs to create new manage objects and persist, patch or delete them. Attribute Values for simple types are the expected ones. For complex attributes such as Roles or Rights, specific objects must be used. They are often provided with an API that makes it easy to construct them.
For more information please refer to the API Javadoc.
Rights Attribute
The Right attribute is composed of Right Grants, that can be constructed according to the example below:
// Simple RightGrant
RightGrant right = RightGrant.of("adm.simple-user")
// Grant on specific Target
RightGrant right = RightGrant.of("app.directory-group").target("LDAP")
// Grant of specific privilege
RightGrant right = RightGrant.of("app.active-directory-group").privilege("admins")
// Grant with specific dimension
RightGrant right = RightGrant.of("feat.user-edit").dimension("confidentiality", 2)
Roles Attribute
TO ADD
Example Script
// One can create new identities from scratch
ApiObject newIdentity = MANAGE.newIdentity().ofType("employee")
.withAttribute(mono("firstName", "John")) // add a mono valued attribute "test1" with value "val1"
.withAttributes( // add multiple attributes in a single call
AttributeValue.mono("lastName", "Smith"),
AttributeValue.multi("email", "jsmith@acme.ltd", "jsmith@mycompany.com")
).create() // create the identity: this is automatically persisted
// One can also patch an existing identity
MANAGE.newPatch().
.set("phoneNumber").value("+33145789632")
.add("email").values("jsmith@othercompany.ltd", "john@smith.com").atIndex(1) // insert new emails at index 1
.add("colors").values("blue", "red").atEnd() // add new values at end
.add("rights").values(RightGrant.of("adm.standard-user")).atEnd() // add new values at end
.remove("email").values("jsmith@acme.ltd") // remove specific values
.replace("email").oldValues("jsmith@mycompany.com")
.with("john.smith@mycompany.com").atEnd() // replace specific values
.replace("colors").oldValues("red")
.with("orange", "green").atIndex(0) // replace specific values at specific index
.patch(newIdentity) // patch the identity: this is automatically persisted
// Finally one can delete an identity (hard delete)
MANAGE.delete(patched)
If you have several attributes to modify, you can build several patch objects and get them executed sequentially.
Or you can also build one patch object with ObjectPatchBuilder, add many operations to it, possibly inserting some business logic, and then get it executed at once.
Example Script
ObjectManager.ObjectPatchBuilder myPatch = MANAGE.newPatch()
if (someCondition) {
myPatch.delete("myFirstAttribute")
}
if (someOtherCondition) {
myPatch.set("mySecondAttribute").value("value for mySecondAttribute")
}
myPatch.patch(OBJECT)
Object Changes
The OperationChanges CHANGES
offers APIs to evaluate attributes changes applied during current Operation between actual OPERATION.object
ApiObject and OPERATION.originalObject
ApiObject (originalObject
may be null, typically for INSERT operations).
For more information please refer to the API Javadoc.
Example Script
Here, all of example script lines using CHANGES return a boolean value
import com.memority.citadel.shared.api.im.identity.RoleAssignmentStatus
import com.memority.citadel.shared.api.im.RightGrant
import com.memority.citadel.shared.api.services.attributes.*
CHANGES.hasAttributeChanged('email')
CHANGES.hasAttributeAdded('newAttr')
CHANGES.hasAttributeRemoved('toBeRemoved')
CHANGES.attributeValueChanges().size() == 6
CHANGES.attributeValueChanges().collect({o -> o.attrId}).containsAll(['toBeRemoved', 'newAttr', 'roles', 'rights', 'multi2', 'email'])
CHANGES.attributeValueChanges('toBeRemoved').getRemovedValues().equals(['obsolete'])
CHANGES.attributeValueChanges('toBeRemoved').getUnchangedValues().isEmpty()
CHANGES.attributeValueChanges('toBeRemoved').getAddedValues().isEmpty()
CHANGES.hasAttributeChanged('multi2')
CHANGES.hasAttributeValueAdded('multi2', '789')
CHANGES.hasAttributeValueAdded('multi2', ['789'])
CHANGES.attributeValueChanges('multi2').getRemovedValues().containsAll(['123', '456'])
CHANGES.attributeValueChanges('multi2').getUnchangedValues().equals(['xxx'])
CHANGES.attributeValueChanges('multi2').getAddedValues().equals(['789'])
CHANGES.hasAttributeValueRemoved('multi2', '123')
CHANGES.hasAttributeValueRemoved('multi2', ['123', '456'])
CHANGES.attributeValueChanges('email').getRemovedValues().equals(['old.address@mail.com'])
CHANGES.attributeValueChanges('email').getUnchangedValues().isEmpty()
CHANGES.attributeValueChanges('email').getAddedValues().equals(['new.address@mail.com'])
CHANGES.hasAttributeValueChangedFrom('email', 'old.address@mail.com') && CHANGES.hasAttributeValueChangedTo('email', 'not.this.one@mail.com')
CHANGES.hasAttributeValueChangedFrom('email', 'old.address@mail.com') && CHANGES.hasAttributeValueChangedTo('email', 'new.address@mail.com')
CHANGES.hasAttributeValueChangedFromTo('email', 'old.address@mail.com', 'not.this.one@mail.com')
CHANGES.hasAttributeValueChangedFromTo('email', 'old.address@mail.com', 'new.address@mail.com')
CHANGES.hasRightsChanged()
CHANGES.hasRightAdded('app.testNewRight')
CHANGES.hasRightGrantAdded({r -> r.getName().equals('app.testNewRight')})
CHANGES.hasRightGrantAdded({r -> r.getName().equals('app.testRight2') && r.getTargets().contains('newTarget')})
CHANGES.hasRightGrantAdded(RightGrant.of('app.testRight2').target('newTarget'))
CHANGES.hasRightRemoved('app.testToBeRemoved')
CHANGES.hasRightGrantRemoved({r -> r.getName().equals('app.testToBeRemoved')})
CHANGES.hasRightGrantRemoved({r -> r.getName().equals('app.testRight2') && r.getTargets().contains('targetToBeRemoved')})
CHANGES.hasRightGrantRemoved(RightGrant.of('app.testToBeRemoved'))
CHANGES.hasRolesChanged()
CHANGES.hasRoleAssignmentAdded({r -> r.role.equals('testNewRole')})
CHANGES.hasRoleAssignmentAdded({r -> r.role.equals('roleId2')})
CHANGES.hasRoleAssignmentAdded({r -> r.role.equals('testNewRole') && r.status == RoleAssignmentStatus.PENDING})
CHANGES.hasRoleAssignmentRemoved({r -> r.role.equals('roleToBeRemoved')})
CHANGES.hasRoleAssignmentRemoved({r -> r.role.equals('roleId')})
CHANGES.hasRoleAssignmentRemoved({r -> r.role.equals('roleToBeRemoved') && r.status == RoleAssignmentStatus.ASSIGNED})
CHANGES.hasRoleAssignmentUpdated({oldRA, newRA -> oldRA.role.equals('roleId') && oldRA.status == RoleAssignmentStatus.PENDING && newRA.role.equals('roleId') && newRA.status == RoleAssignmentStatus.INACTIVE})
CHANGES.hasRoleAssigned('testNewRoleAssigned')
CHANGES.hasRoleRevoked('roleToBeRemoved')
Logging
Provides an ApiRuleLogger under the name LOG
It extends SLF4J logger and augment it with audit(..)
specific level methods which respect the same signatures as other log level methods.
public interface ApiRuleLogger extends Logger {
void audit(String msg);
void audit(String format, Object arg);
void audit(String format, Object arg1, Object arg2);
void audit(String format, Object... arguments);
void audit(String msg, Throwable t);
boolean isAuditEnabled();
}
Reporting
All log levels (except for “audit” which is described below) send their output to the standard output (console) and to a reporting collection through a builtin configuration.
Reporting configuration id | Collection name |
---|---|
appLog | app_log |
// Some examples of standard log levels sent both to console and reporting
LOG.info("some log")
LOG.error("some error", e)
Customizing log level threshold for reporting
One can define the maximum level at which logs are sent to the reporting using settings which accept the log level threshold.
Services | Setting | Default |
---|---|---|
BUM |
|
|
IDM |
|
|
Log levels are organized in the following ascending order: OFF
ERROR
WARN
INFO
DEBUG
TRACE
ALL
This means that with bum.app-logs.level.rule=INFO
all logs from rule scripts executed in the BUM service with a level lower or equal to INFO
(namely INFO
, WARN
and ERROR
) will be sent to the reporting. The other log levels will only be available in the standard output.
The OFF
level would completely prevent logs to be sent to the reporting.
Audit
Logs that need to be audited must be sent using the “AUDIT
” level which is not part of the hierarchy of standard levels we saw before. An explicit call to audit(..)
will always send the formatted log event to the audit service.
LOG.audit("some audit of this object: {}", objectToAudit.toString())
Customizing a rule logger name
Since the logger name can be a convenient criteria to search a particular log, it is possible to provide a custom logger name inside a script instead of using the default one (the default logger name is “[RULE]”).
@LoggerName("myLoggerName") // The @LoggerName should be positionned on a package declaration, the actual name of the package being of no significance for rule executions
package my.script.rule
LOG.info("Some info")
LOG.error("Some error")
LOG.audit("Some audit")
The logger name will be available in the “loggerName” property of the reporting document and can be used as a criteria. This allows to specify a unique logger name per rule if needed.
Note that the formatted log ("%name %date %level [%class:%line] %msg%n"
) of the standard output and the audit will also use this logger name if provided.
User interface
Logs can be consulted in the “System > Logs” menu entry of the admnistration portal.
This requires the permission “AdmPortal:app-logs”
Reference Tables
The ReferenceTable
API is described in details in a separate guide: Guide: Reference Tables.
Json serialization
There is a helper function to serialize an object to its JSON representation.
Example Script
toJson(['id':OBJECT.id])
As an example the result is :
{"id":"myObjectId"}
Notifications
Action rules can access a notification service to, well, send notifications.
NotificationService
/**
* A notification service provides the ability to send notifications using the Memority notification facility.<br>
* Usage:</br>
* <code>
* notificationService.create()
* .withType("some-notification-type")
* .withNotifications(new HashSet<>(Arrays.asList("notif&", "notif2"))
* .withActor("jdoe") //uid
* .role("some-role")
* .name("John Doe")
* .email("jdoe@acme.com")
* .speaking("fr")
* .end()
* .withPayload(new HashMap<String,String(){{ ... }})
* .send();
* </code>
*/
public interface NotificationService {
/**
* Starts the creation of the notification to send
* @return a notification builder
*/
NotificationBuilder create();
/**
* If a context is set, this method will send the event using the information
* in the context
*/
void sendNotification(String... notificationIds);
/**
* Build a notification event. The event is constructed from the context
* set with the {@link RuleContextAware#setContext(Context)} method and this
* method allows to expand or override elements.
*
* @param notificationIds a list of notification ids.
*/
NotificationBuilder buildNotification(String... notificationIds);
}
Example Script
NOTIFY.create()
.withType("some-notification-type)
.withNotifications(new HashSet<>(Arrays.asList("notif&", "notif2"))
.withActor("jdoe")
.role("some-role")
.name("John Doe")
.email("jdoe@acme.com")
.speaking("fr")
.end()
.withPayload(new HashMap<String,String(){{ ... }})
.send();
One can also use well known notification event which will have a default behaviour according to the context.
NOTIFY.sendNotification("myNotificationIdWithEventTypeCITADEL_IDENTITY_CREATION");
// If an override is wanted, use buildNotification instead
NOTIFY.buildNotification("myNotificationIdWithEventTypeCITADEL_IDENTITY_CREATION")
.withActor("jdoe1")
.role("some-role1")
.email("jdoe1@acme.com")
.end()
.withActor("jdoe2")
.role("some-role2")
.email("jdoe2@acme.com")
.end()
.send();
Random Passwords
A PasswordGeneratorProvider
is exposed in Groovy scripts via the PASSWORD
binding.
PasswordGeneratorProvider
/**
* The Groovy entry point to generate passwords.
* <p>
* For example, to generate a password of 12 chars including lower cased chars and digits:
* <pre>
* PASSWORD
* .length(12)
* .with(LOWER_CASE_LETTERS)
* .with(DIGITS)
* .generate()
* </pre>
*
* To generate a password of 12 chars with at least 5 lower cased chars and 3 digits:
* <pre>
* PASSWORD
* .length(12)
* .with(5, LOWER_CASE_LETTERS)
* .with(3, DIGITS)
* .generate()
* </pre>
*
* To generate a password of 8 chars beginning with 5 lower cased chars and ending with 3 digits:
* <pre>
* PASSWORD
* .with(5, LOWER_CASE_LETTERS)
* .with(3, DIGITS)
* .generate()
* </pre>
*/
public interface PasswordGeneratorProvider {
/**
* Set the password length. If this method is not called, then the sum of the "length" argument passed when
* calling {@link #with(int, PasswordCharacterClass)} multiple times is considered.
*/
PasswordGeneratorApi length(int length);
/**
* The password will include at least this number of class of characters (letters, digits, etc.)
*
* @param length the minimum number of characters of the given class
* @param characterClass the class of characters (letters, digits, etc.)
*/
PasswordGeneratorApi with(int length, PasswordCharacterClass characterClass);
/**
* A shortcut to {@link #with(int, PasswordCharacterClass)} with {@code length = 1}
*/
default PasswordGeneratorApi with(PasswordCharacterClass characterClass) {
return with(1, characterClass);
}
}
PasswordCharacterClass
public enum PasswordCharacterClass {
/**
* From "a" to "z".
*/
LOWER_CASE_LETTERS,
/**
* From "A" to "Z" but excluding "I" and "O" to avoid confusions with "1" and "0" respectively.
*/
UPPER_CASE_LETTERS,
/**
* The combination of {@link #LOWER_CASE_LETTERS} and {@link #UPPER_CASE_LETTERS}.
*/
LETTERS,
/**
* From "2" to "9". "0" and "1" are excluded to avoid confusions with "O" and "I" respectively.
*/
DIGITS,
/**
* The combination of {@link #LETTERS} and {@link #DIGITS}.
*/
LETTERS_OR_DIGITS,
/**
* Special characters {@value PasswordGenerator.Characters#PUNCTUATION}.
*/
PUNCTUATION;
}
Example Script
def password = PASSWORD
.length(12)
.with(1,LOWER_CASE_LETTERS)
.with(1,UPPER_CASE_LETTERS)
.with(1,DIGITS)
.with(1,PUNCTUATION)
.generate()
BUM API
ApiAccessCodeManager
/**
* Service to manage an access code
*/
public interface ApiAccessCodeManager {
/**
* Creates a new Access Code
* @return an {@link AccessCodeBuilder}
*/
AccessCodeBuilder newAccessCode();
/**
* Peeks at the currently set access code if there is one.
* @return the optional access code
*/
ApiAccessCode get();
/**
* Send notifications for a given access code
* @param accessCode access code
* @param notificationIds list of notification identifiers
*/
void sendCreationNotifications(ApiAccessCode accessCode, String... notificationIds);
/**
* Access Code builder
*/
interface AccessCodeBuilder {
/**
* Specifies the access code type
* @param type the access code type
* @return this builder
*/
AccessCodeBuilder ofType(String type);
/**
* Specifies the user identity id
* @param identity the identity id
* @return this builder
*/
AccessCodeBuilder forIdentityId(String identity);
/**
* Specifies the intents
* @param intents the intents
* @return this builder
*/
AccessCodeBuilder withIntents(String... intents);
/**
* Specifies the access code lifetime (in days)
* @param lifetime the lifetime in days
* @return this builder
*/
AccessCodeBuilder withLifetimeInDays(long lifetime);
/**
* Specifies the access code lifetime (in hours)
* @param lifetime the lifetime in hours
* @return this builder
*/
AccessCodeBuilder withLifetimeInHours(long lifetime);
/**
* Specifies the access code lifetime (in minutes)
* @param lifetime the lifetime in minutes
* @return this builder
*/
AccessCodeBuilder withLifetimeInMinutes(long lifetime);
/**
* Specifies the access code lifetime (in seconds)
* @param lifetime the lifetime in seconds
* @return this builder
*/
AccessCodeBuilder withLifetimeInSeconds(long lifetime);
/**
* Specifies the access code data
* @param data the data
* @return this builder
*/
AccessCodeBuilder withData(Map<String, String> data);
/**
* Specifies the access code use count
* @param useCount the use count
* @return this builder
*/
AccessCodeBuilder useCount(int useCount);
/**
* Specifies the access code count strategy
* @param strategy the strategy
* @return this builder
*/
AccessCodeBuilder useStrategy(AccessCodeUseStrategy strategy);
/**
* Finalizes the Access Code creation
* @return an API Access Code
*/
ApiAccessCode create();
}
}
/**
* Represents an AccessCode
*/
public interface ApiAccessCode {
/**
* @return the access code unique identifier
*/
String getId();
/**
* @return the target identity identifier
*/
String getIdentityId();
/**
* @return the expiration date of the access code
*/
Instant getUntil();
/**
* The intents describe the task the code gives access to. They usually map to feature identifiers.
*
* @return the list of intents the code gives access to
*/
List<String> getIntents();
/**
* The type of access code acts as a marker. Policies can be applied on types.
*
* @return the access code type.
*/
String getType();
/**
* Additional data that may be used in the context of the access code task executions.
* @return
*/
Map<String, String> getData();
/**
* @return the current access code use count
*/
int getUseCount();
/**
* @return <code>true</code> if the access code has already been used, <code>false</code> otherwise
*/
boolean isUsed();
/**
* Only when the strategy is TASK should the access code use count be managed in an Action.
*
* @return the access code use strategy
*/
AccessCodeUseStrategy getUseStrategy();
/**
* Use the current access count, incrementing its use count by one.
*
* @return the access code, with an incremented use count
*/
ApiAccessCode use();
}
ApiFeatureManager
/**
* A service that provides methods to perform some operations on Features and Workflows
*/
public interface ApiFeatureManager {
/**
* Sets the feature id
*/
FeatureExecutionRequestBuilder newExecutionFor(String featureId);
/**
* Feature Execution Request Builder
*/
interface FeatureExecutionRequestBuilder {
/**
* Sets the object id
*/
FeatureExecutionRequestBuilder withObjectId(String objectId);
/**
* Sets the given field to a specified value(s)
*/
FeatureExecutionRequestBuilder withField(String fieldId, Object... values);
/**
* Creates a new ObjectPatch
*/
FeatureExecutionPatchBuilder withPatch();
/**
* Finalizes and executes the FeatureExecutionRequest being built.
*/
ApiFeatureExecution execute();
/**
* Finalizes and schedules the FeatureExecutionRequest being built.
*/
ApiFeatureExecution executeAt(Instant scheduleDate);
}
/**
* Feature Execution Patch Builder
*/
interface FeatureExecutionPatchBuilder extends GenericPatchBuilder<FeatureExecutionPatchBuilder> {
/**
* Finalizes the {@link ObjectPatch}.
* @return this builder
*/
FeatureExecutionPatchBuilder end();
}
/**
* Represents a Feature Execution
*/
interface ApiFeatureExecution {
String getId();
String getFeatureId();
String getFeatureType();
String getObjectId();
String getStatus();
String requesterId();
Instant getScheduleDate();
ObjectKind getObjectKind();
}
}
/**
* Builds an {@link ObjectPatch} (Common interface for FeatureExecutionPatchBuilder and ObjectPatchBuilder).
*/
public interface GenericPatchBuilder<B extends GenericPatchBuilder<?>> {
/**
* Delete the specified attribute, removing all its values.
* @param attribute the attribute to delete.
* @return this builder.
*/
B delete(String attribute);
/**
* Sets the given attribute to a specified value(s). Usage:
* <pre>
* objectManager.patch(obj)
* .set("monoValuedAttr").value("foo")
* .set("multivaluedAttr").values("bar", "baz")
* ...
* </pre>
* @param attribute the attribute to set.
* @return a {@link SetAttributePatchBuilder}, to specify the new attribute values.
* @see SetAttributePatchBuilder
*/
SetAttributePatchBuilder<B> set(String attribute);
/**
* Adds the specified values to the given attribute, either at the end or at the specified index. Usage:<br/>
* <pre>
* objectManager.patch(obj)
* .add("multivaluedAttr1").values("foo", "bar").atEnd()
* .add("multivaluedAttr2").values("baz").atIndex(42)
* ...
* </pre>
* @param attribute the attribut to which the values will be added
* @return an {@link AddAttributePatchBuilder}, to specify which values to add and where.
* @see AddAttributePatchBuilder
*/
AddAttributePatchBuilder<B> add(String attribute);
/**
* Removes the specified values from the given attribute. Usage:<br/>
* <pre>
* objectManager.patch(obj)
* .remove("multivaluedAttr").values("foo", "bar")
* </pre>
* @param attribute the attribute to remove the values from.
* @return a {@link RemoveAttributePatchBuilder}, to specify which values to remove.
* @see RemoveAttributePatchBuilder
*/
RemoveAttributePatchBuilder<B> remove(String attribute);
/**
* Replaces old values with new ones for the given attribute; this is equivalent to calling
* {@link #remove(String)} then {@link #add(String)}. Usage:
* <pre>
* objectManager.patch(obj)
* .replace("multivaluedAttr1").oldValues("foo", "bar").with("baz").atEnd()
* .replace("multivaluedAttr2").oldValues("foo", "bar").with("baz").atIndex(42)
* </pre>
* @param attribute the attribute to replace the values from.
* @param <T> the attribute values type.
* @return a {@link ReplacePatchOldValuesBuilder}, to specify what to remove, what to add and where.
* @see ReplacePatchOldValuesBuilder
*/
<T> ReplacePatchOldValuesBuilder<B, T> replace(String attribute);
/**
* Creates an {@link AttributeOperation#ADD ADD}
* {@link AttributePatch} for the given attribute.
*
* @see ObjectManager.PatchBuilder#add(String)
*/
interface AddAttributePatchBuilder<B extends GenericPatchBuilder<?>> {
/**
* Specifies the values to add.
* @param values the values to add
* @param <T> the type of the values to add
* @return an {@link ObjectManager.IndexedPatchBuilder}, to specify where to add the values.
* @see ObjectManager.IndexedPatchBuilder
*/
<T> IndexedPatchBuilder<B> values(List<T> values);
/**
* @see #values(List)
*/
@SuppressWarnings("unchecked")
<T> IndexedPatchBuilder<B> values(T... values);
}
/**
* Creates a {@link AttributeOperation#REPLACE REPLACE}
* {@link AttributePatch} for the given attribute. This builder is responsible for setting setting the values to
* remove.
*
* @see ObjectManager.PatchBuilder#replace(String)
*/
interface ReplacePatchOldValuesBuilder<B extends GenericPatchBuilder<?>, T> {
/**
* Specifies the values to remove.
* @param oldValues the values to remove.
* @return a {@link ObjectManager.ReplacePatchNewValuesBuilder}, to specify the values to add.
*/
ReplacePatchNewValuesBuilder<B, T> oldValues(List<T> oldValues);
/**
* @see #oldValues(List)
*/
@SuppressWarnings("unchecked")
ReplacePatchNewValuesBuilder<B, T> oldValues(T... oldValues);
}
/**
* Creates a {@link AttributeOperation#REPLACE REPLACE}
* {@link AttributePatch} for the given attribute. This builder is responsible for setting setting the values to
* add.
*
* @see ObjectManager.PatchBuilder#replace(String)
*/
interface ReplacePatchNewValuesBuilder<B extends GenericPatchBuilder<?>, T> {
/**
* Specifies the values to add.
* @param newValues the values to add.
* @return an {@link ObjectManager.IndexedPatchBuilder}, to specify where the values should be added
*/
IndexedPatchBuilder<B> with(List<T> newValues);
/**
* @see #with(List)
*/
@SuppressWarnings("unchecked")
IndexedPatchBuilder<B> with(T... newValues);
}
/**
* Helper builder for {@link ObjectManager.AddAttributePatchBuilder} and {@link ObjectManager.ReplacePatchNewValuesBuilder} to specify where to
* add the new values.
*/
interface IndexedPatchBuilder<B extends GenericPatchBuilder<?>> {
/**
* Specifies to add the new values at the given index and finalizes the {@link AttributePatch} being built.
* @param index where new values are inserted (0 is the first index)
* @return the parent {@link ObjectManager.PatchBuilder}.;
*/
B atIndex(int index);
/**
* Specifies to append the new values to the current attribute values and finalizes the {@link AttributePatch}
* being built.
* @return the parent {@link ObjectManager.PatchBuilder}.;
*/
B atEnd();
}
/**
* Creates a {@link AttributeOperation#REMOVE REMOVE}
* {@link AttributePatch} for the given attribute.
*
* @see ObjectManager.PatchBuilder#remove(String)
*/
interface RemoveAttributePatchBuilder<B extends GenericPatchBuilder<?>> {
/**
* Specifies the values to remove and finalizes the attribute patch being built.
* @param values the values to remove.
* @param <T> the type of the attribute values.
* @return the parent {@link ObjectManager.PatchBuilder}
*/
<T> B values(List<T> values);
/**
* @see #values(List)
*/
@SuppressWarnings("unchecked")
<T> B values(T... values);
}
/**
* Creates a {@link AttributeOperation#SET SET}
* {@link AttributePatch} for the given attribute.
*
* @see ObjectManager.PatchBuilder#set(String)
*/
interface SetAttributePatchBuilder<B extends GenericPatchBuilder<?>> {
/**
* Specifies the new values of the attribute, and finalizes the {@link AttributePatch} being built.
* @param values the new values of the attribute
* @param <T> the values' type
* @return the parent patch builder
*/
<T> B values(List<T> values);
/**
* @see #values(List)
*/
@SuppressWarnings("unchecked")
<T> B values(T... values);
/**
* Specifies the new values of the attribute, and finalizes the {@link AttributePatch} being built.
* @param value the new value of the attribute
* @param <T> the value type
* @return the parent patch builder
*/
<T> B value(T value);
}
}
ApiMyMFAManager
/*
* Copyright (c) 2016-2020 Accenture. All Rights Reserved.
*
* This file is part of Memority <http://www.memority.com>, an Accenture Security project.
*
* Unauthorized copying of this file, via any medium is strictly prohibited.
* Proprietary and confidential.
*/
package com.memority.citadel.shared.api.services.myMFA;
import com.memority.citadel.shared.api.im.MyMFAUser;
/**
* This service provides methods for managing MyMFA accounts
*/
public interface ApiMyMFAManager {
/**
* Deletes the MyMFA user associated to the given identity id.
* In addition, the MyMFA attributes (login, id) of the identity are deleted and the MyMFA authentication status is reset.
*
* @param identityId the identity id
*/
void deleteUser(String identityId);
/**
* Gets the {@link MyMFAUser} associated to the given id
* @param identityId identity id
* @return an optional {@link MyMFAUser}. If unknown user, returns null
*/
MyMFAUser getUser(String identityId);
/**
* Check if the user account is suspended.
* @param identityId identity id
* @return true if the account is suspended or the user does not exists
*/
boolean isSuspended(String identityId);
/**
* Suspend the user account.
* If the user does not exist, return without failure.
* This will set the {@link MyMFAUser#getStatus()} to "SUSPENDED".
* Unlike activation and deactivation, suspension is only checked by the MyMFA third party service.
* @param identityId identity id
*/
void suspendUser(String identityId);
/**
* Unsuspend the user account.
* If the user does not exist, return without failure.
* @see ApiMyMFAManager#suspendUser(String)
* @param identityId identity id
*/
void unsuspendUser(String identityId);
/**
* Adds the MyMFA authentication right
* @param identityId the identity Id
*/
void activate(String identityId);
/**
* Deletes the MyMFA user and removes the authentication right
* @param identityId the identity Id
*/
void deactivate(String identityId);
/**
* Returns if the identity has the MyMFA authentication right
*
* @param identityId identity id
* @return true if activated, otherwise false
*/
boolean isActivated(String identityId);
/**
* Returns if the identity has at least 1 active device
* @param identityId identity id
* @return true if some devices are active, otherwise false
*/
boolean hasActiveDevice(String identityId);
/**
* Sends a recovery code to the given identity id
* @param identityId identity id
* @return a {@link RecoveryCodeRequestBuilder}
*/
RecoveryCodeRequestBuilder sendRecoveryCode(String identityId);
/**
* Deletes an existing device on the given identity id
* @param identityId the identity id
* @param deviceId the device id
*/
void deleteUserDevice(String identityId, Long deviceId);
/**
* Common interface for Notification Configuration
* @param <B> parent builder
*/
interface WithNotification<B extends WithNotification<?>> {
/**
* Sets the notification id
* @param notificationId notification id
* @return the parent builder
*/
B withNotificationId(String notificationId);
/**
* Sets an email.
* If not set, the user primary email will be used
* @param email an email
* @return the parent builder
*/
B withEmail(String email);
/**
* Sends the notification
*/
void send();
}
/**
* Recovery Code Request Builder
*/
interface RecoveryCodeRequestBuilder extends WithNotification<RecoveryCodeRequestBuilder> {
}
}
ApiRecertification
package com.memority.citadel.shared.api.services.recertification;
import java.time.Instant;
/**
* Role Assignment Recertification API available
*/
public interface ApiRecertification {
/**
* Estimates the next recertification date of a given Role Assignment
*
* @param identityId the Identity id
* @param roleAssignmentId the Role Assignment id
* @return the estimated next recertification date, or <code>null</code> if the Role Assignment is not subject to
* campaign or on the fly recertification
*/
Instant estimateNextRecertificationDate(String identityId, String roleAssignmentId);
}
ApiRecertificationCampaign
package com.memority.citadel.shared.api.services.recertification;
import java.util.List;
/**
* Role Assignment Recertification Campaign API
*/
public interface ApiRecertificationCampaign {
/**
* Launches an ad hoc recertification campaign for given Identities and Roles
*
* @param name the campaign name (mandatory)
* @param description the campaign description
* @param identities the Identities to re-certify (mandatory)
* @param roles the Roles to re-certify (mandatory)
* @return the campaign execution id
*/
String launchAdHocRecertificationCampaign(String name,
String description,
List<String> identities,
List<String> roles);
}
ApiRoleAssignmentManager
package com.memority.citadel.shared.api.services.roleAssignment;
import com.memority.citadel.shared.api.im.identity.RoleAssignment;
import com.memority.citadel.shared.api.services.FieldBuilder;
import java.time.Instant;
import java.util.Map;
/**
* A service for managing Role Assignments
* <br>
* An instance is provided in the API_ROLE_ASSIGNMENT Groovy context.
*/
public interface ApiRoleAssignmentManager {
/**
* Request a Role for a user
*
* @param roleId the requested Role id
* @return a {@link RoleAssignmentRequestBuilder}
*/
RoleAssignmentRequestBuilder requestRole(String roleId);
/**
* Update a user's Role Assignment
*
* @param identityId the user id
* @param assignmentId the Role Assignment id
* @return a {@link RoleAssignmentUpdateRequestBuilder}
*/
RoleAssignmentUpdateRequestBuilder updateRoleAssignment(String identityId, String assignmentId);
/**
* Revoke a user's Role Assignment
*
* @param identityId the user id
* @param assignmentId the Role Assignment id
* @return a {@link RoleAssignmentDeletionRequestBuilder}
*/
RoleAssignmentDeletionRequestBuilder revokeRoleAssignment(String identityId, String assignmentId);
/**
* Request Builder
*/
interface RoleAssignmentRequestBuilder extends RoleAssignmentCommonBuilderOptions<RoleAssignmentRequestBuilder>,
WithDimension<RoleAssignmentRequestBuilder> {
/**
* Specifies the identity to whom the role will be given (mandatory)
* @param identityId the identity id
* @return this builder
*/
RoleAssignmentRequestBuilder forIdentityId(String identityId);
/**
* Finalizes the request
* @return the created Role Assignment
*/
RoleAssignment submit();
}
/**
* Update Request Builder
*/
interface RoleAssignmentUpdateRequestBuilder extends RoleAssignmentCommonBuilderOptions<RoleAssignmentUpdateRequestBuilder>,
WithDimension<RoleAssignmentUpdateRequestBuilder> {
/**
* Finalizes the request
* @return the update Role Assignment
*/
RoleAssignment submit();
}
/**
* Deletion Request Builder
*/
interface RoleAssignmentDeletionRequestBuilder {
/**
* Specifies the requester (mandatory)
* @param requester the requester
* @return this builder
*/
RoleAssignmentDeletionRequestBuilder onBehalfOf(String requester);
/**
* Configures a field with the given id
* @param fieldId the field id
* @return a {@link FieldBuilder}
*/
FieldBuilder<RoleAssignmentDeletionRequestBuilder> withField(String fieldId);
/**
* Finalizes the request
*/
void submit();
}
/**
* Common Builder options
* @param <B> the builder class
*/
interface RoleAssignmentCommonBuilderOptions<B> {
/**
* Specifies the requester (mandatory)
* @param requester the requester
* @return this builder
*/
B onBehalfOf(String requester);
/**
* Specifies the comment
* @param comment a comment
* @return this builder
*/
B withComment(String comment);
/**
* Specifies the enabled from
* @param enabledFrom enabled from value
* @return this builder
*/
B enabledFrom(Instant enabledFrom);
/**
* Specifies the enabled until
* @param enabledUntil enabled until value
* @return this builder
*/
B enabledUntil(Instant enabledUntil);
/**
* Configures a field with the given id
* @param fieldId the field id
* @return a {@link FieldBuilder}
*/
FieldBuilder<B> withField(String fieldId);
}
/**
* Dimension Builder
* @param <B> the builder class
*/
interface WithDimension<B> {
/**
* Specifies a dimension with the given id and values
* @param dimensionId dimension id
* @param dimensionValues dimension values
* @return this builder
*/
B withDimension(String dimensionId, Object... dimensionValues);
/**
* Specifies a dimension with the given id and value
* @param dimensionId dimension id
* @param dimensionValue dimension value
* @return this builder
*/
B withDimension(String dimensionId, Object dimensionValue);
/**
* Specifies the dimensions
* @param dimensions dimensions
* @return this builder
*/
B withDimensions(Map<String, Object> dimensions);
}
}
FieldBuilder
package com.memority.citadel.shared.api.services;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
/**
* Field builder
* @param <B> the parent builder class
*/
public interface FieldBuilder<B> {
/**
* Configures a String field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeString();
/**
* Configures an Integer field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, Long> ofTypeInteger();
/**
* Configures a Float field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, Double> ofTypeFloat();
/**
* Configures a DateTime field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, Instant> ofTypeDatetime();
/**
* Configures a Date field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, LocalDate> ofTypeDate();
/**
* Configures a Boolean field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, Boolean> ofTypeBoolean();
/**
* Configures a Binary field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeBinary();
/**
* Configures an Identity Ref field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeIdentityRef();
/**
* Configures an Organization Ref field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeOrganizationRef();
/**
* Configures a Resource Ref field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeResourceRef();
/**
* Configures a Role Ref field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeRoleRef();
/**
* Configures a Role Publication Ref field value
* @return a {@link FieldValueBuilder}
*/
FieldValueBuilder<B, String> ofTypeRolePublicationRef();
/**
* Field value builder
* @param <B> the parent builder class
* @param <T> the Field value type
*/
interface FieldValueBuilder<B, T> {
/**
* Specifies the field value
* @param value the value
* @return the parent builder
*/
B value(T value);
/**
* Specifies the field values
* @param values the values
* @return the parent builder
*/
@SuppressWarnings("unchecked")
B values(T... values);
/**
* Specifies the field values
* @param values the values
* @return the parent builder
*/
B values(List<T> values);
}
}
Examples
List Widget Rule
import com.memority.toolkit.rest.client.groovy.ContentType
int pageNumber = LIST_WIDGET.request.pagination.getIndex()
def pageSize = LIST_WIDGET.request.pagination.size
def params = [:] << [_page:"$pageNumber", _limit:"$pageSize"]
if (LIST_WIDGET.request.sort != null) {
params << [_sort:"${LIST_WIDGET.request.sort.property}", _order:"${LIST_WIDGET.request.sort.direction}"]
}
def result;
try {
result = HTTP.get('http://localhost:3000/users') { contentType ContentType.JSON queryParams params }
} catch(Exception e) {
return ListWidgetRuleResult.failure("Failed to connect to the server");
}
if (result.isSuccess()) {
def content = result.json as List
return ListWidgetRuleResult.success(content, pageNumber, pageSize, result.headers.get("X-Total-Count").get(0) as int)
}
def i18nArgs = [:] << [code:"${result.code}"]
ListWidgetRuleResult.failure("An error occurred", "tenant.optional.i18nKey", i18nArgs)