The info on this page applies to Grouper v4 and above. See Also Grouper Provisioning Framework |
The LDAP provisioner in Grouper v4+ replaces the old PSPNG.
The major differences from PSPNG is how groups are identified as provisionable, bulk operations for performance, using sync objects for caching and performance, configuration using the UI, etc.
How can I provision a boolean to LDAP
RDN value adjusting group name (remove prefix)
${edu.internet2.middleware.grouper.util.GrouperUtil.ldapEscapeRdnValue(edu.internet2.middleware.grouper.util.GrouperUtil.stripPrefix(grouperProvisioningGroup.name, 'w:e:'))} |
DN adjusting group name (remove prefix)
${edu.internet2.middleware.grouper.util.GrouperUtil.ldapBushyDn(edu.internet2.middleware.grouper.util.GrouperUtil.stripPrefix(grouperProvisioningGroup.name, 'w:e:'), 'cn', 'ou', true, false) + ',ou=groups,dc=school,dc=edu'} |
Fields and attributes can have default values. This is particularly important for some LDAP products that always require a member for groups. In other words, if you have a group in Grouper with no members and that is provisioned to LDAP, the LDAP group must have a member. You can have a default value for the member attribute that could either be some DN in your LDAP system. Or if supported by your LDAP, you could use <emptyString> if the default value should be an empty string.
Lets craft some best practices
Best practice | Description |
---|---|
If using AD, use bushy provisioning | CN in AD can only be 64 which the group name frequently is longer than |
If bushy provisioning, provision the group name somehow | An attribute could hold the group name, or sAMAccountName could hold group name with colons as underscores |
Should put gidNumber or Grouper UUID in an LDAP attribute | Helps with renaming |
If you want to track which groups are from which provisioners you could provision the provisionerId to an attribute | Could be used in a filter or so its easy to see which grouper provisioner created/or/manages that group Note: you dont need this if all groups for provisioner are in a dedicated OU |
These are the supported LDAP DAO operations. The operations that are actually called are based on how you've configured the provisioner. For example, you may or may not want Grouper to insert entities to LDAP.
retrieveAllGroups
retrieveGroups
insertGroup
deleteGroup
updateGroup
retrieveAllEntities
retrieveEntities
insertEntity
deleteEntity
updateEntity
retrieveMembership
retrieveMembershipsByGroups
If you are troubleshooting issues, you can enable low level Ldaptive logging in the Advanced configuration to get details about the actual operations sent to LDAP and the responses. The logs will go to the container logs.
Example:
2021-11-04 15:28:40,182: [Thread-21] INFO GrouperProvisioningLogCommands.infoLog(25) - - Command log for provisioner 'ldapProvTest' - 'u5vmmuhb', retrieveAllEntities: Ldaptive searchRequest: [org.ldaptive.SearchRequest@-349354149::baseDn=ou=People,dc=example,dc=edu, searchFilter=[org.ldaptive.SearchFilter@1043503778::filter=(&(objectClass=person)(uid=*)), parameters={}], returnAttributes=[cn, objectClass, sn, uid], searchScope=SUBTREE, timeLimit=0, sizeLimit=0, derefAliases=null, typesOnly=false, binaryAttributes=null, sortBehavior=UNORDERED, searchEntryHandlers=null, searchReferenceHandlers=null, controls=null, referralHandler=null, intermediateResponseHandlers=null] |
Run an ldap filter
import edu.internet2.middleware.grouper.ldap.*; GrouperSession grouperSession = GrouperSession.startRootSession(); List<LdapEntry> ldapEntries = LdapSessionUtils.ldapSession().list("personLdap", "ou=People,dc=example,dc=edu", LdapSearchScope.SUBTREE_SCOPE, "uid=*", (String[])(Object)GrouperUtil.toArray("uid"), null); System.out.println(GrouperUtil.length(ldapEntries)); |
Run this:
./gsh.sh -pspngAttributesToProvisioningAttributes pspngConfigId provisioningFrameworkConfigId readonly|notReadonly deleteOrphans|dontDeleteOrphans |
deleteOrphans means remove provisioning framework provisionable assignments that do not exist in pspng. dontDeleteOrphans means leave those alone and the provisioning framework provisionable assignments are potentially a superset of pspng
Once the new provisioner is provisioning the data, turn off the pspng jobs
Sync objects can cache information in LDAP. The cached data below are examples. You can cache whatever data you want. Synced from full sync (if doesnt exist or if errors), incremental (if doesnt exist or if errors), and the nightly (scheduled) subject resolution daemon (full refresh)
Object | Field | Cached data |
---|---|---|
gcGrouperSyncGroup | groupToId2 | group DN |
gcGrouperSyncGroup | groupToId3 | whatever attribute value the user attribute refers to |
gcGrouperSyncGroup | groupFromId2 | ldap group object attribute value that looks up group |
gcGrouperSyncMember | memberToId2 | user DN |
gcGrouperSyncMember | memberToId3 | whatever attribute value the group attribute refers to users as |
gcGrouperSyncMember | memberFromId2 | ldap person object attribute value that looks up user |
gcGrouperSyncMember | memberFromId3 | subject attribute value that helps look up user |
Common config attributes for LDAP are below.
Config | Example | Description | Notes |
---|---|---|---|
class | edu.internet2.middleware.grouper.app.ldapProvisioning.LdapSync | Class extends the base provisioner class | This class informs configuration decisions. Required. Read-only. |
hasSubjectLink | true false | If the subject API is needed to resolve attribute on subject | required, drives requirements of other configurations. defaults to false. |
hasTargetUserLink | true false | If subjects need to be resolved in the target before provisioning | defaults to false. required. show if groupMemberships |
hasTargetGroupLink | true false | If groups need to be resolved in the target before provisioning | defaults to false. required. show if userAttributes |
subjectSourcesToProvision | pennperson | subject sources to provision | required. defaults to all except g:gsa, grouperExternal, g:isa, localEntities. comma separated list. checkboxes. |
createMissingUsers | true or false | defaults false. required. show if userAttributes or hasTargetUserLink | |
createMissingGroups | true or false | defaults to true. required. show if groupMemberships or hasTargetGroupLink | |
deleteInTargetIfInTargetAndNotGrouper | true or false | if groups in full sync should be deleted if in group all filter and not in grouper or for attributes delete other attribute not provisioned by grouper | default to false |
deleteInTargetIfDeletedInGrouper | true or false | if groups that were created in grouper were deleted should it be deleted in ldap? or for attributes, delete attribute value if deleted in grouper | default to true |
membershipFields | members read,admin update,admin admin | if provisioning normal memberships or privileges | default to "members" for normal memberships |
userSearchFilter | ldap example: (&(objectClass=person)(uid=${targetEntity.retrieveAttributeValue('uid')})) | how to find a user | optional. show if userAttributes or hasTargetUserLink |
userSearchAllFilter | ldap example: (&(objectClass=person)(uid=*)) | filter users when searching all | optional. show if userAttributes or hasTargetUserLink |
groupSearchFilter | ldap example: (&(objectClass=group)(gidNumber=${targetGroup.retrieveAttributeValue('gidNumber')})) | find a single group (other than by DN) | optional. show if groupMemberships or hasTargetGroupLink |
groupSearchAllFilter | ldap example: (&(objectclass=group)(gidNumber=*)) | find all groups | optional. show if groupMemberships or hasTargetGroupLink |
refreshGroupLinkIfLessThanAmount | 20 | refresh target group link if less than this amount | show if hasTargetGroupLink |
common.groupLink.groupFromId2 | ${targetGroup.getName()} | Target group link - groupFromId2 | show if hasTargetGroupLink |
common.groupLink.groupFromId3 | Target group link - groupFromId3 | show if hasTargetGroupLink | |
common.groupLink.groupToId2 | Target group link - groupToId2 | show if hasTargetGroupLink | |
common.groupLink.groupToId3 | Target group link - groupToId3 | show if hasTargetGroupLink | |
refreshEntityLinkIfLessThanAmount | 20 | refresh target user link if less than this amount | show if hasTargetUserLink |
common.entityLink.memberFromId2 | ${targetEntity.getName()} | Target user link - memberFromId2 | optional show if hasTargetUserLink |
common.entityLink.memberFromId3 | Target user link - memberFromId3 | optional show if hasTargetUserLink | |
common.entityLink.memberToId2 | Target user link - memberToId2 | optional show if hasTargetUserLink | |
common.entityLink.memberToId3 | Target user link - memberToId3 | optional show if hasTargetUserLink | |
refreshSubjectLinkIfLessThanAmount | 20 | refresh subject link if less than this amount | show if hasSubjectLink |
common.subjectLink.memberFromId2 | Subject link - memberFromId2 | optional show if hasSubjectLink | |
common.subjectLink.memberFromId3 | Subject link - memberFromId3 | optional show if hasSubjectLink | |
common.subjectLink.memberToId2 | Subject link - memberToId2 | optional show if hasSubjectLink | |
common.subjectLink.memberToId3 | Subject link - memberToId3 | optional show if hasSubjectLink | |
groupAllowedToAssign | (group name) | group allowed to assign only grouper system admin is allowed when no specific group is allowed to assign the given target | optional |
allowAssignmentsOnlyOnOneStem | true or false | allow assignment only on one stem | default to false |
readOnly | true or false | read only | default to false |
debugLog | true or false | enable debug log | default to false |
logAllObjectsVerbose | true or false | log all objects verbose | default to false |
targetEntityAttributeCount | 5 | number of attributes for users | default to 0 Should be 0-20 show if userAttributes or hasTargetUserLink |
targetGroupAttributeCount | 5 | number of attributes for groups | default to 0 Should be 0-20 show if groupMemberships or hasTargetGroupLink |
targetEntityAttribute.[0-19].name | uid | name of the attribute | required |
targetEntityAttribute.[0-19].isFieldElseAttribute | true or false | is field else attribute? | default to false |
targetEntityAttribute.[0-19].valueType | string | value type | required Should be string or int or long |
targetEntityAttribute.[0-19].insert | true or false | insert attribute? | default to false |
targetEntityAttribute.[0-19].update | true or false | update attribute? | default to false |
targetEntityAttribute.[0-19].delete | true or false | delete attribute? | default to false |
targetEntityAttribute.[0-19].select | true or false | select attribute? | default to false |
targetEntityAttribute.[0-19].matchingId | true or false | matching id attribute? | default to false |
targetEntityAttribute.[0-19].multiValued | true or false | multi-valued attribute? | default to false |
targetEntityAttribute.[0-19].membershipAttribute | true or false | is this the membership attribute? | default to false |
targetEntityAttribute.[0-19].translateExpressionFromMembership | ${gcGrouperSyncGroup.getGroupToId2()} | translate expression from membership | show and require if membershipAttribute |
targetEntityAttribute.[0-19].translateExpressionCreateOnly | ${grouperUtil.toSet('top', 'person')} | translate expression when creating objects only | show if not membershipAttribute and insert = true |
targetEntityAttribute.[0-19].translateExpression | ${grouperProvisioningEntity.getSubjectId()} | translate expression otherwise | show and require if not membershipAttribute |
targetGroupAttribute.[0-19].name | gidNumber | name of the attribute | required |
targetGroupAttribute.[0-19].isFieldElseAttribute | true or false | is field else attribute? | default to false |
targetGroupAttribute.[0-19].valueType | long | value type | required Should be string or int or long |
targetGroupAttribute.[0-19].insert | true or false | insert attribute? | default to false |
targetGroupAttribute.[0-19].update | true or false | update attribute? | default to false |
targetGroupAttribute.[0-19].delete | true or false | delete attribute? | default to false |
targetGroupAttribute.[0-19].select | true or false | select attribute? | default to false |
targetGroupAttribute.[0-19].matchingId | true or false | matching id attribute? | default to false |
targetGroupAttribute.[0-19].multiValued | true or false | multi-valued attribute? | default to false |
targetGroupAttribute.[0-19].membershipAttribute | true or false | is this the membership attribute? | default to false |
targetGroupAttribute.[0-19].translateExpressionFromMembership | ${gcGrouperSyncMember.getMemberToId2()} | translate expression from membership | show and require if membershipAttribute |
targetGroupAttribute.[0-19].translateExpressionCreateOnly | ${grouperUtil.toSet('top', 'posixGroup')} | translate expression when creating objects only | show if not membershipAttribute and insert = true |
targetGroupAttribute.[0-19].translateExpression | ${grouperProvisioningGroup.getIdIndex()} | translate expression otherwise | show and require if not membershipAttribute |
Should we use DAO for LDAP so we can unit test everything but the LDAP stuff? Also for dry run?
Yes
Make a bushy DN and take off a prefix
${edu.internet2.middleware.grouper.util.GrouperUtil.ldapBushyDn(edu.internet2.middleware.grouper.util.GrouperUtil.stripPrefix(grouperProvisioningGroup.name, 'org:something:'), 'cn', 'ou', true, false) + ',OU=Grouper,DC=else,DC=something,DC=net'} |