This page documents using the Grouper Loader to load a group from LDAP. This is available in Grouper v2.1 and later
Grouper loader LDAP configuration
The Grouper loader LDAP configuration is done through the "new attribute framework". You can assign the grouperLoaderLdap attribute on a group, and the configuration attributes on that assignment. Note, these attributes are in the attribute root stem name (default "etc:attribute"), in a subfolder named "loaderLdap"). By default only Grouper admins can assign or edit these attributes, though an admin could delegate that permission to someone else. Be very careful of the security implications (they could run any ldap filter to load their group, which could be sensitive data). Note, all LDAP jobs are scheduled as crons. These attributes are automatically created on Grouper started if they don't exist if the grouper.properties setting: grouper.attribute.loader.autoconfigure is set to true.
Attribute system name |
Attribute display name |
Required? |
Description |
Assignable to |
Value type |
Example value |
---|---|---|---|---|---|---|
grouperLoaderLdap |
Grouper loader LDAP |
required |
This is the marker attribute that you assign to a group to mark is as a grouper loader ldap group |
Groups |
None |
|
grouperLoaderLdapType |
Grouper loader LDAP type |
required |
Like the SQL loader, this holds the type of job from the GrouperLoaderType enum, currently the only valid values are LDAP_SIMPLE, LDAP_GROUP_LIST, LDAP_GROUPS_FROM_ATTRIBUTES. Simple is a group loaded from LDAP filter which returns subject ids or identifiers. Group list is an LDAP filter which returns group objects, and the group objects have a list of subjects. Groups from attributes is an LDAP filter that returns subjects which have a multi-valued attribute e.g. affiliations where groups will be created based on subject who have each attribute value |
grouperLoaderLdap |
Enum |
LDAP_SIMPLE |
grouperLoaderLdapServerId |
Grouper loader LDAP server ID |
required |
Server ID that is configured in the grouper-loader.properties that identifies the connection information to the LDAP serve. Note, if you use "dn", and dn is not an attribute of the object, then the fully qualified object name will be used |
grouperLoaderLdap |
String |
personLdap (note: depends on your configuration) |
grouperLoaderLdapFilter |
Grouper loader LDAP filter |
required |
LDAP filter returns objects that have subjectIds or subjectIdentifiers and group name (if LDAP_GROUP_LIST) |
grouperLoaderLdap |
String |
(affiliation=student) |
grouperLoaderLdapSubjectAttribute |
Grouper loader LDAP subject attribute name |
required, for LDAP_SIMPLE, and LDAP_GROUP_LIST |
Attribute name of the filter object result that holds the subject id. |
grouperLoaderLdap |
String |
hasMember, or personId |
grouperLoaderLdapGroupAttribute |
Grouper loader LDAP group attribute name |
required for LDAP_GROUPS_FROM_ATTRIBUTE |
Attribute name of the filter object result that holds the group name |
grouperLoaderLdap |
String |
affiliation |
grouperLoaderLdapSearchDn |
Grouper loader LDAP search base DN |
optional |
Location that constrains the subtree where the filter is applicable. Note, this is relative to the base DN in the ldap server config in the grouper-loader.properties for this server. This makes the query more efficient |
grouperLoaderLdap |
String |
ou=people |
grouperLoaderLdapQuartzCron |
Grouper loader LDAP quartz cron |
required |
Quartz cron config string, e.g. every day at 8am is: 0 0 8 * * ? |
grouperLoaderLdap |
String |
0 0 8 * * ? |
grouperLoaderLdapSourceId |
Grouper loader LDAP source ID |
optional |
Source ID from the sources.xml that narrows the search for subjects. This is optional though makes the loader job more efficient |
grouperLoaderLdap |
String |
schoolPeople |
grouperLoaderLdapSubjectIdType |
Grouper loader LDAP subject ID type |
optional |
The type of subject ID. This can be either: subjectId (most efficient, default), subjectIdentifier (2nd most efficient), or subjectIdOrIdentifier |
grouperLoaderLdap |
Enum |
subjectId, subjectIdentifier, subjectIdOrIdentifier |
grouperLoaderLdapSearchScope |
Grouper loader LDAP search scope |
optional |
How the deep in the subtree the search will take place. Can be OBJECT_SCOPE, ONELEVEL_SCOPE, or SUBTREE_SCOPE (default) |
grouperLoaderLdap |
Enum |
OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE |
grouperLoaderLdapAndGroups |
Grouper loader LDAP require in groups |
optional |
If you want to restrict membership in the dynamic group based on other group(s), put the list of group names here comma-separated. The require groups means if you put a group names in there (e.g. school:community:employee) then it will 'and' that group with the member list from the loader. So only members of the group from the loader query who are also employees will be in the resulting group |
grouperLoaderLdap |
String |
school:community:employee |
grouperLoaderLdapPriority |
Grouper loader LDAP scheduling priority |
optional |
Quartz has a fixed threadpool (max configured in the grouper-loader.properties), and when the max is reached, then jobs are prioritized by this integer. The higher the better, and the default if not set is 5. |
grouperLoaderLdap |
Integer |
5 |
grouperLoaderLdapGroupsLike |
Grouper loader LDAP groups like |
optional, for LDAP_GROUP_LIST only |
This should be a sql like string (e.g. school:orgs:%org%_systemOfRecord), and the loader should be able to query group names to |
grouperLoaderLdap |
String |
school:orgs:%org%_systemOfRecord |
grouperLoaderLdapExtraAttributes |
Grouper loader LDAP extra attributes |
optional, for LDAP_GROUP_LIST |
Attribute names (comma separated) to get LDAP data for expressions in group name, displayExtension, description |
grouperLoaderLdap |
String |
name, description |
grouperLoaderLdapErrorUnresolvable |
Grouper loader LDAP error unresolvable |
optional |
Value could be true or false (default to true). If true, then there will be an error if there are unresolvable subjects in the results. If you know there are subjects in LDAP which are not resolvable by Grouper, set to false, they will be ignored |
grouperLoaderLdap |
boolean |
true or false (default to true) |
grouperLoaderLdapGroupNameExpression |
Grouper loader LDAP group name expression |
optional, for LDAP_GROUP_LIST, or LDAP_GROUPS_FROM_ATTRIBUTES |
JEXL expression language fragment that evaluates to the group name (relative in the stem as the group which has the loader definition) |
grouperLoaderLdap |
String |
someFolder:${groupAttribute['name']} |
grouperLoaderLdapGroupDisplayExtensionExpression |
Grouper loader LDAP group display extension expression |
optional, for LDAP_GROUP_LIST, or LDAP_GROUPS_FROM_ATTRIBUTES |
JEXL expression language fragment that evaluates to the group display extension |
grouperLoaderLdap |
String |
${groupAttribute['displayName']} |
grouperLoaderLdapGroupDescriptionExpression |
Grouper loader LDAP group description expression |
optional, for LDAP_GROUP_LIST, or LDAP_GROUPS_FROM_ATTRIBUTES |
JEXL expression language fragment that evaluates to the group description |
grouperLoaderLdap |
String |
Auto-created based on LDAP group ${dn} |
LDAP server configuration
Configure LDAP servers in the grouper-loader.properties:
################################# ## LDAP connections ################################# # specify the ldap connection with user, pass, url # the string after "ldap." is the ID of the connection, and it should not have # spaces or other special chars in it. In this case is it "personLdap" #note the URL should start with ldap: or ldaps: if it is SSL. #It should contain the server and port (optional if not default), and baseDn, #e.g. ldaps://ldapserver.school.edu:636/dc=school,dc=edu #ldap.personLdap.url = ldaps://ldapserver.school.edu:636/dc=school,dc=edu #optional, if authenticated #ldap.personLdap.user = uid=someapp,ou=people,dc=myschool,dc=edu #optional, if authenticated note the password can be stored encrypted in an external file #ldap.personLdap.pass = secret #optional, if you are using tls, set this to true. Generally you will not be using an SSL URL to use TLS... #ldap.personLdap.tls = false #optional, if using sasl #ldap.personLdap.saslAuthorizationId = #ldap.personLdap.saslRealm = #optional (note, time limit is for search operations, timeout is for connection timeouts), #most of these default to vt-ldap defaults. times are in millis #validateOnCheckout defaults to true if all other validate methods are false #ldap.personLdap.batchSize = #ldap.personLdap.countLimit = #ldap.personLdap.timeLimit = #ldap.personLdap.timeout = #ldap.personLdap.minPoolSize = #ldap.personLdap.maxPoolSize = #ldap.personLdap.validateOnCheckIn = #ldap.personLdap.validateOnCheckOut = #ldap.personLdap.validatePeriodically = #ldap.personLdap.validateTimerPeriod = #ldap.personLdap.pruneTimerPeriod = #if connections expire after a certain amount of time, this is it, in millis, defaults to 300000 (5 minutes) #ldap.personLdap.expirationTime =
Here is an example config], with serverId: myLdap
ldap.myLdap.user = uid=someUser,ou=people,dc=school,dc=edu ldap.myLdap.pass = abcdabcd ldap.myLdap.url = ldaps://ldap.school.edu:636/dc=school,dc=edu
Configuration
You can configure the loader LDAP attributes with the new UI, GSH (example below), or WS. Here is a screenshot of the UI
Configure LDAP loader settings in grouper-loader.properties
#################################################################### ## LDAP loader settings ################################## # el classes to add to the el context for the EL to calculate subejct ids or group names etc. # Comma-separated fully qualified classnamesm will be registered by the non-fully qualified # uncapitalized classname. So you register a.b.SomeClass, it will be available by variable: someClass loader.ldap.el.classes =
LDAP_SIMPLE test case
Find two groups in your LDAP that do not have many members
In this case, here is a group in ldap:
cn=test:ldapTesting:test1,ou=groups,dc=upenn,dc=edu
With hasMember attribute values:
netmon
There is another group in ldap:
cn=test:testGroup,ou=groups,dc=upenn,dc=edu
With hasMember attribute values:
choate mchyzer harveycg mchyzer
Make sure the member ids in LDAP are subject ids or identifiers in Grouper. In this case, I am using a new instance of Grouper, not my institutional one, so I will just make a new source, and insert dummy data, with subject identifiers (even though subject id's are more efficient)
Run this SQL (note, this is tested in mysql, but will work with small adjustment in any db)
CREATE TABLE `person_source_v` ( `penn_id` VARCHAR(50) NOT NULL, `name` VARCHAR(50) DEFAULT NULL, `email` VARCHAR(200) DEFAULT NULL, `description` VARCHAR(50) DEFAULT NULL, `pennname` VARCHAR(50) DEFAULT NULL, `description_lower` VARCHAR(50) DEFAULT NULL, PRIMARY KEY (`penn_id`) ); INSERT INTO `person_source_v`(`penn_id`,`name`,`description`,`pennname`,`description_lower`) VALUES ('44567890','Chris Hyzer','Chris Hyzer','mchyzer','chris hyzer, mchyzer, 44567890'), ('11234567','John Smith','John Smith','jsmith','john smith, jsmith, 11234567'), ('12345678','Bryan Hall','Bryan Hall','bwh','bryan hall, bwh, 12345678'), ('22345678','Bill Choate','Bill Choate','choate','bill choate, choate, 22345678'), ('33456789','Craig Harvey','Craig Harvey','harveycg','craig harvey, harveycg, 33456789') ; COMMIT;
Here is the sources.xml config:
<source adapterClass="edu.internet2.middleware.subject.provider.JDBCSourceAdapter2"> <id>pennperson</id> <name>Penn person</name> <type>person</type> <init-param> <param-name>jdbcConnectionProvider</param-name> <param-value>edu.internet2.middleware.grouper.subj.GrouperJdbcConnectionProvider</param-value> </init-param> <init-param> <param-name>maxResults</param-name> <param-value>1000</param-value> </init-param> <init-param> <param-name>dbTableOrView</param-name> <param-value>person_source_v</param-value> </init-param> <init-param> <param-name>subjectIdCol</param-name> <param-value>penn_id</param-value> </init-param> <init-param> <param-name>nameCol</param-name> <param-value>name</param-value> </init-param> <init-param> <param-name>descriptionCol</param-name> <param-value>description</param-value> </init-param> <init-param> <!-- search col where general searches take place, lower case --> <param-name>lowerSearchCol</param-name> <param-value>description_lower</param-value> </init-param> <init-param> <!-- optional col if you want the search results sorted in the API (note, UI might override) --> <param-name>defaultSortCol</param-name> <param-value>description</param-value> </init-param> <init-param> <!-- col which identifies the row, perhaps not subjectId --> <param-name>subjectIdentifierCol0</param-name> <param-value>pennname</param-value> </init-param> <init-param> <param-name>subjectIdentifierCol1</param-name> <param-value>penn_id</param-value> </init-param> <!-- now you can count up from 0 to N of attributes for various cols --> <init-param> <param-name>subjectAttributeCol0</param-name> <param-value>pennname</param-value> </init-param> <init-param> <param-name>subjectAttributeName0</param-name> <param-value>PENNNAME</param-value> </init-param> <init-param> <param-name>subjectAttributeCol1</param-name> <param-value>email</param-value> </init-param> <init-param> <param-name>subjectAttributeName1</param-name> <param-value>EMAIL</param-value> </init-param> <init-param> <param-name>sortAttribute0</param-name> <param-value>description</param-value> </init-param> <init-param> <param-name>searchAttribute0</param-name> <param-value>description_lower</param-value> </init-param> </source>
Configure an LDAP connection in the grouper-loader.properties:
ldap.personLdap.url = ldaps://penngroups.upenn.edu/dc=upenn,dc=edu ldap.personLdap.user = uid=someone,ou=entities,dc=upenn,dc=edu ldap.personLdap.pass = xxxxxxxx
Now you can configure the loader in GSH:
gsh 0% grouperSession = GrouperSession.startRootSession(); gsh 1% group = new GroupSave(grouperSession).assignName("someStem:myLdapGroup").assignCreateParentStemsIfNotExist(true).save(); gsh 2% attributeAssign = group.getAttributeDelegate().assignAttribute(LoaderLdapUtils.grouperLoaderLdapAttributeDefName()).getAttributeAssign(); #in case you need to retrieve again, here is an example gsh 2% attributeAssign = group.getAttributeDelegate().retrieveAssignment(null, LoaderLdapUtils.grouperLoaderLdapAttributeDefName(), false, true); gsh 3% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapTypeName(), "LDAP_SIMPLE"); gsh 4% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapFilterName(), "(|(cn=test:testGroup)(cn=test:ldaptesting:test1))"); # every minute so it is easy to test gsh 5% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapQuartzCronName(), "0 * * * * ?"); gsh 6% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSearchDnName(), "ou=groups"); gsh 7% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapServerIdName(), "personLdap"); gsh 8% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSourceIdName(), "pennperson"); gsh 10% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSubjectAttributeName(), "hasMember"); # NOTE: hopefully you can use subjectId instead, it will improve the performance a LOT! gsh 11% attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSubjectIdTypeName(), "subjectIdentifier"); gsh 12% group = GroupFinder.findByName(grouperSession, "someStem:myLdapGroup"); gsh 13% loaderRunOneJob(group); loader ran successfully, inserted 5 memberships, deleted 0 memberships, total membership count: 5 gsh 14% getMembers("someStem:myLdapGroup"); member: id='22345678' type='person' source='pennperson' uuid='360802a1bdf341859109c086ffe79022' member: id='33456789' type='person' source='pennperson' uuid='5dd1fc0431214a6fa53bf3cb7790d5ea' member: id='44567890' type='person' source='pennperson' uuid='8b26f3fb43da4661946282227580d5be' member: id='12345678' type='person' source='pennperson' uuid='db43860f64004ec295129cde994a450d' member: id='10000000' type='person' source='pennperson' uuid='ea9b420cca1f43b1a1cb8b682cb3624a' gsh 5% delMember("someStem:myLdapGroup", "22345678"); true gsh 16% addMember("someStem:myLdapGroup", "GrouperSystem"); true gsh 17% loaderRunOneJob(group); loader ran successfully, inserted 1 memberships, deleted 1 memberships, total membership count: 5 gsh 18%
TODO
- Take apart DN for subject ID
- Affiliation attribute to make groups (query returns users with that attribute)
sdf