All Downloads are FREE. Search and download functionalities are using the official Maven repository.

api.citadel.citadel-api.3.41.2.source-code.citadel-rules.gdsl Maven / Gradle / Ivy

Go to download

This artifact provides the API classes that are necessary to implement general configuration Rules on the Memority IM platform.

There is a newer version: 3.43.1
Show newest version
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.tree.TokenSet
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod

import java.util.regex.Matcher

import static com.intellij.openapi.util.text.StringUtil.unquoteString

/**
 * Citadel rule DSL definition, for automagic autocompletion in "modern" IDEs (aka. IntelliJ).
 * Usage:
 * 
    *
  • Set the toolkit.rule.flavor maven property to toolkit,citadel
  • *
  • OR start your groovy script with //@ruleFlavor:toolkit,citadel *
*/ class Constants { static final String SEARCH_EXPRESSION_DSL_METHOD = "expr" static final String SEARCH_EXPRESSION_HAS_ROLE_MATCHING_METHOD = "hasRoleMatching" static final String BUILTIN_ATTRIBUTE_IDS = "com.memority.citadel.shared.api.im.BuiltinAttributeIds" static final String META_ATTRIBUTE_IDS = "com.memority.citadel.shared.api.im.MetaAttributeIds" static final String CUSTOM_ATTRIBUTE_IDS = "com.memority.toolkit.rule.groovyHints.citadel.CustomAttributeIds" static final String CITADEL_API_TYPE_ANNOTATION = "com.memority.citadel.shared.api.im.CitadelApiType" static final String CITADEL_SEARCH_CRITERION_ATTRIBUTE_ANNOTATION = "com.memority.citadel.shared.api.im.CitadelSearchCriterionAttribute" static final ROLE_ASSIGNMENT_NON_SEARCHABLE_ATTRIBUTES = ["identity", "metadata", "roleBinding"] as Set static final String ID_PATTERN_STR = $/[a-zA-Z]|[a-zA-Z0-9_]+[a-zA-Z0-9\-_]*[a-zA-Z0-9]/$ static final String CUSTOM_ATTRIBUTE_MAGIC_COMMENT = $///\s*@customAttribute\s*:\s*(${ID_PATTERN_STR})\s*=\s*(.*)\s*/$ static final String CUSTOM_SEARCHABLE_ATTRIBUTE_MAGIC_COMMENT = $///\s*@customSearchableAttribute\s*:\s*(${ID_PATTERN_STR})\s*=\s*(.*)\s*/$ } /** * Contexts */ //noinspection GroovyAssignabilityCheck contribute(context(scope: scriptScope())) { if (!ruleFlavor(place, "citadel")) { return } //Contexts property(name: "CTXT", type: "com.memority.citadel.shared.api.context.CitadelContext", doc: 'The parent context made available to all Citadel rules.') property(name: "OPERATION", type: "com.memority.citadel.shared.api.context.OperationContext", doc: ''' This context provides information about an operation performed on a Managed Object. It holds all of the current object apiObject, potentially being worked on, as well as the original apiObject, as existing in the IDM repository (if available). ''') property(name: "PRIMARY_OPERATION", type: "com.memority.citadel.shared.api.context.OperationContext", doc: ''' This context provides information about the primary operation of an Object or Business Policy when handling the secondary Scope. ''') property(name: "OBJECT", type: "com.memority.citadel.shared.api.im.ApiObject", doc: 'The IM object being worked on.') property(name: "ATTRIBUTE", type: "com.memority.citadel.shared.api.context.AttributeContext", doc: ''' The context of an individual attribute, when running while processing a single attribute. Note that this attribute may be processed in the context of an IM object, in which case an accompanying OPERATION context is also available.

Note: this context is immutable.

''') property(name: "FEATURE", type: "com.memority.citadel.shared.api.context.FeatureContext", doc: 'Context information about the Feature being executed.') property(name: "EXTERNAL", type: "com.memority.citadel.shared.api.context.ExternalContext", doc: 'Additional information sent by the API caller, that will be returned as is') property(name: "SUBJECT", type: "com.memority.citadel.shared.api.im.ApiObject", doc: 'The authenticated principal, if any.') property(name: "REQUESTER", type: "com.memority.citadel.shared.api.im.ApiObject", doc: 'The current operation\'s requester') property(name: "WORKFLOW", type: "com.memority.citadel.shared.api.context.WorkflowContext", doc: 'Context information about the Workflow being executed.') property(name: "DIMENSION", type: "com.memority.citadel.shared.api.context.DimensionContext", doc: 'Context information for Rule-based right/role dimension mapping') property(name: "ROLE_REQUEST", type: "com.memority.citadel.shared.api.context.RoleRequestContext", doc: 'Context information for Rule-based Role Workflow Strategy') property(name: "ROLE_ASSIGNMENT", type: "com.memority.citadel.shared.api.context.RoleAssignmentOperationContext", doc: 'Context information about the role assignment operation') //APIs property(name: "FIND", type: "com.memority.citadel.shared.api.services.finder.ObjectFinderProvider", doc: 'Provides an {@link ObjectFinder} for each Object Kind') property(name: "SEQ", type: "com.memority.citadel.shared.api.services.seq.SequenceProvider", doc: 'Provides access to the sequences that are managed by the IDM.') property(name: "REF", type: "com.memority.citadel.shared.api.services.reftable.ReferenceTableDataFinderProvider", doc: ''' Provides access access to a given ReferenceTable's rows, using simple semantics.

It is the main entry point to reference table data through the Citadel API.

''') property(name: "NOTIFY", type: "com.memority.citadel.shared.api.services.notification.NotificationService", doc: ''' Provides the ability to send notifications using the Citadel notification facility.
Usage:
notificationService.create()
    .withType("some-notification-type)
    .withNotifications(new HashSet<>(Arrays.asList("notif1", "notif2"))
    .withActor("jdoe")
        .role("some-role")
        .name("John Doe")
        .email("[email protected]")
        .speaking("fr")
    .end()
    .withPayload(new HashMap
 ''')
    property(name: "MANAGE",
            type: "com.memority.citadel.shared.api.services.manager.ObjectManager",
            doc: 'Service to instantiate, save, patch and and delete managed objects.')

    property(name: "ACCESS_CODE",
            type: "com.memority.citadel.shared.api.services.accesscode.ApiAccessCodeManager",
            doc: 'This service allows to handle the access code lifecycle (use an access code, handle its expiration...)')

    // TODO CTD-9248 remove
    property(name: "API_INWEBO",
            type: "com.memority.citadel.shared.api.services.inwebo.ApiInWeboManager",
            doc: 'This service allows to manage InWebo accounts (delete, unlock, send enrollment link...)')

    property(name: "API_MYMFA",
            type: "com.memority.citadel.shared.api.services.myMFA.ApiMyMFAManager",
            doc: 'This service allows to manage MyMFA accounts (delete, unlock, send enrollment link...)')

    property(name: "API_FEATURE",
            type: "com.memority.citadel.shared.api.services.feature.ApiFeatureManager",
            doc: 'This service allows to execute features')

    property(name: "API_WORKFLOW",
            type: "com.memority.citadel.shared.api.services.workflow.ApiWorkflow",
            doc: 'This service allows to manage workflow execution')

    property(name: "CHANGES",
            type: "com.memority.citadel.shared.api.services.attributes.OperationChanges",
            doc: 'Provides a {@link OperationChanges}')

    property(name: "API_REPORTING",
            type: "com.memority.citadel.shared.api.services.reporting.ApiReportingManager",
            doc: 'This service allows to execute operations on reporting collections')

    property(name: "REPORTING_DOCUMENT",
            type: "com.memority.citadel.shared.api.services.reporting.ApiReportingDocument",
            doc: 'Holds the current reporting document in some contexts such as display conditions')

    property(name: "API_RECERTIFICATION",
            type: "com.memority.citadel.shared.api.services.recertification.ApiRecertification",
            doc: 'The Role Assignment Recertification API')

    property(name: "API_RECERTIFICATION_CAMPAIGN",
            type: "com.memority.citadel.shared.api.services.recertification.ApiRecertificationCampaign",
            doc: 'The Role Assignment Recertification Campaign API')

    property(name: "API_OBJECT_RECERTIFICATION",
            type: "com.memority.citadel.shared.api.services.recertification.ApiObjectRecertification",
            doc: 'The Object Recertification API')

    property(name: "API_OBJECT_RECERTIFICATION_CAMPAIGN",
            type: "com.memority.citadel.shared.api.services.recertification.ApiObjectRecertificationCampaign",
            doc: 'The Object Recertification Campaign API')

    property(name: "API_OTP",
            type: "com.memority.citadel.shared.api.services.otp.ApiOtpManager",
            doc: 'Synchronize OTP modules registrations')

    property(name: "API_ROLE_ASSIGNMENT",
            type: "com.memority.citadel.shared.api.services.roleAssignment.ApiRoleAssignmentManager",
            doc: 'The Role Assignment API (Request/Update/Revoke Role Assignments)')
}

/**
 * Built in and meta attributes typing
 */
//noinspection GroovyAssignabilityCheck
contribute(context(ctype: 'com.memority.citadel.shared.api.im.ApiObject')) {
    if (!(ruleFlavor(place, "citadel") || ruleFlavor(place, "domino"))) {
        return
    }

    findAttributes(findClass(Constants.META_ATTRIBUTE_IDS))
            .each { property(name: it.name, type: it.groovyType, doc: "Meta attribute") }

    findAttributes(findClass(Constants.BUILTIN_ATTRIBUTE_IDS))
            .each { property(name: it.name, type: it.groovyType, doc: "Builtin attribute") }

    findCustomAttributes(place, findClass(Constants.CUSTOM_ATTRIBUTE_IDS))
            .each { property(name: it.name, type: it.groovyType, doc: "Custom attribute") }
}

/**
 * Search expression DSL: builtin and meta attributes completion and hasRoleMatching support
 */
//noinspection GroovyAssignabilityCheck
contribute(context(scope: closureScope(isArg: true))) {
    if (!(ruleFlavor(place, "toolkit") && (ruleFlavor(place, "citadel") || ruleFlavor(place, "domino")))) {
        return
    }

    def call = enclosingCall(Constants.SEARCH_EXPRESSION_DSL_METHOD) as GrMethodCallExpression
    def callBind = call?.bind()

    if (!(callBind instanceof GrMethod && !callBind?.containingClass)) {
        return
    }

    if (!(call.children.size() == 3 && call.children[2] instanceof GrClosableBlock)) {
        return
    }

    method(name: Constants.SEARCH_EXPRESSION_HAS_ROLE_MATCHING_METHOD,
            type: "com.memority.toolkit.core.api.search.expression.FunctionExpression",
            params: [expression: "com.memority.toolkit.core.api.search.expression.SearchExpression"],
            doc: '''
Search expression DSL function to match identities having a role assignment matching a given search expression.
Example:
expr {firstName.eq('John') & hasRoleMatching(role.eq('admin') & role.type.eq('appRole') & dimensions.contains('{"answer": 42}'))} ''') // Would have loved to have different completions based on the enclosing function call // Unfortunately, IntelliJ doesn't handle this and conditions on `place` is not epileptic-friendly // -> contribute all builtin, custom and meta attributes as well as RoleAssignment fields for hasRoleMatching def allAttributes = [] as Set allAttributes.addAll( findClass("com.memority.citadel.shared.api.im.identity.RoleAssignment") .allFields .findAll { !Constants.ROLE_ASSIGNMENT_NON_SEARCHABLE_ATTRIBUTES.contains(it.name) } .collect { it.name } ) //ease completion with all builtin/meta attribute ids allAttributes.addAll( findAttributes(findClass(Constants.META_ATTRIBUTE_IDS)) .findAll { it.searchCriterion } .collect { it.name } ) allAttributes.addAll( findAttributes(findClass(Constants.BUILTIN_ATTRIBUTE_IDS)) .findAll { it.searchCriterion } .collect { it.name } ) allAttributes.addAll( findCustomAttributes(place, findClass(Constants.CUSTOM_ATTRIBUTE_IDS)) .findAll { it.searchCriterion } .collect { it.name } ) allAttributes .sort() .each { property(name: it, type: "com.memority.toolkit.core.api.search.expression.SearchExpressions.OperatorExpressionBuilder", doc: 'Search expression prop \'' + it + '\'.
' + '
' + 'Equivalent to prop("' + it + '").') } } class Attribute { final String name final String groovyType final boolean searchCriterion Attribute(String name, String groovyType, boolean searchCriterion) { this.name = name this.groovyType = groovyType this.searchCriterion = searchCriterion } } static List findAttributes(PsiClass attributeIdsClass) { attributeIdsClass.allFields .findAll { it.initializer instanceof PsiLiteralExpression } .findAll { it.getAnnotation(Constants.CITADEL_API_TYPE_ANNOTATION) } //not annotated, do not advertise .collect { def apiTypeAnnot = it.getAnnotation(Constants.CITADEL_API_TYPE_ANNOTATION) def searchCriterionAnnot = it.getAnnotation(Constants.CITADEL_SEARCH_CRITERION_ATTRIBUTE_ANNOTATION) def type = unquoteString(apiTypeAnnot.findAttributeValue("value").text) def attributeName = unquoteString(it.initializer.text) return new Attribute(attributeName, type, searchCriterionAnnot != null) } } /** * Finds the custom object attributes using the following strategies: *
    *
  • Adds all fields of the com.memority.citadelTenant.groovyHints.ApiObjectHints class
  • *
  • //@customAttribute: attributeName = type magic comments
  • *
  • //@customSearchableAttribute: attributeName = type magic comments
  • *
* Magic comments take precedence on ApiObjectHints. * * @param place the place to look at (typically the place member available in contribute closures * @param attributeIdsClass the ApiObjectHints class (because `findClass is only available in `contribute` closure) * @return a name -> type map */ static List findCustomAttributes(PsiElement place, PsiClass attributeIdsClass) { def result = [] as List if (attributeIdsClass) { result.addAll(findAttributes(attributeIdsClass)) } result.addAll( findMagicComments(place, Constants.CUSTOM_ATTRIBUTE_MAGIC_COMMENT) .collect { new Attribute(it.group(1), it.group(2), false) } ) result.addAll( findMagicComments(place, Constants.CUSTOM_SEARCHABLE_ATTRIBUTE_MAGIC_COMMENT) .collect { new Attribute(it.group(1), it.group(2), true) } ) return result } /** * Look for "magic" comments at the beginning of the script matching a given regex * @param place the place to look at (typically the place member available in contribute closures * @param re the regex to match * @return a list of matchers for each matching comment */ static List findMagicComments(PsiElement place, String re) { if (!place.containingFile.node) { return [] } def result = [] as List loop: for (def ast : place.containingFile.node.getChildren(TokenSet.ANY)) { sw: //noinspection GroovyFallthrough switch (ast.elementType.toString()) { case "new line": case "WHITE_SPACE": case "PACKAGE_DEFINITION": case "IMPORT": continue loop case "line comment": def magicComment = (ast as PsiComment).text def matcher = magicComment =~ re if (matcher.matches()) { result << matcher } break sw default: break loop } } return result } /** * Checks that the enclosing groovy script has a given "rule flavor". It looks for, in order: *
    *
  • A //@ruleFlavor:flavor1,flavor2,... magic comment *
  • The toolkit.rule.flavor
  • Maven property *
* @param place the place to look at (typically the place member available in contribute closures * @param flavor the desired flavor */ static boolean ruleFlavor(PsiElement place, String flavor) { def enabledFlavors = null //check for magic comment def magicCommentMatchers = findMagicComments(place, $///\s*@ruleFlavor\s*:\s*([\w,\s]+)\s*/$) if (magicCommentMatchers) { enabledFlavors = magicCommentMatchers.last().group(1) } //if no magic comment, check maven property if (!enabledFlavors) { def project = place.manager.project def file = place.context.containingFile.originalFile.containingFile.virtualFile //Groovy fragment: get original file if (file instanceof LightVirtualFile && file.originalFile) { file = file.originalFile } if (file) { //noinspection GrUnresolvedAccess def mavenProject = project .getServiceByClassName("org.jetbrains.idea.maven.project.MavenProjectsManager") .findContainingProject(file) if (mavenProject) { //noinspection GroovyAssignabilityCheck enabledFlavors = (mavenProject.properties.getProperty("toolkit.rule.flavor") as String) } } } return enabledFlavors && enabledFlavors.split(",") .collect { it.trim() } .contains(flavor) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy