LDAP Subject API example
There is a newer, preferred approach
As of Grouper v4, the recommended way to add a subject source is the web-based wizard in the UI: "Miscellaneous" → "Subject sources" → "Actions" → "Add subject source". For LDAP, the wizard generates configuration for the newer GrouperLdapSourceAdapter2_5 adapter (the ldaptive-based LDAP source, available v2.5.40+).
This page documents the older GrouperJndiSourceAdapter approach, configured by hand in subject.properties. It still works in current releases (confirmed in v4 through v7), but some newer features may be lacking.
Overview
This is a complete worked example of configuring an LDAP subject source in Grouper, pointing at a public LDAP directory. It shows the subject.properties entries for the GrouperJndiSourceAdapter, including the search filters and a virtual attribute that concatenates two LDAP attributes into a description.
Version: the subject.properties overlay format shown here has been the standard since v2.3 (it replaced sources.xml). The GrouperJndiSourceAdapter class is still present in all currently supported releases.
Privileges: editing subject sources through the UI requires a Grouper sysadmin (wheel or root) account. Editing subject.properties directly requires server-side file access and a Grouper restart to pick up the change.
Public LDAP example
Carnegie Mellon University formerly ran a public LDAP server, which was hooked up as a subject source for this example.
Setting | Value |
|---|---|
Server |
|
Base DN |
|
URL |
|
Top OU |
|
Example user |
|
The relevant directory attributes:
Attribute | Notes |
|---|---|
|
|
| common name (First Last) |
| email address |
| school / college name |
| netId (used as an identifier) |
This example builds a description that is the concatenation of the name and the school/college name — it appends the school/college name if present, and just uses the name if not.
CMU's public LDAP server (ldap.andrew.cmu.edu) is no longer reachable — the hostname no longer resolves in DNS — so this is an illustrative example only. Use it as a template for the subject.properties syntax against your own LDAP directory rather than as a live, runnable source.
subject.properties
The subject source configuration. It uses Grouper configuration overlays: the base settings live in subject.base.properties (do not edit) and are overlaid by subject.properties.
# Copyright 2016 Internet2
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Subject configuration
#
# The subject properties uses Grouper Configuration Overlays (documented on wiki)
# By default the configuration is read from subject.base.properties
# (which should not be edited), and the subject.properties overlays
# the base settings. See the subject.base.properties for the possible
# settings that can be applied to the subject.properties
# enter the location of the sources.xml. Must start with classpath: or file:
# blank means dont use sources.xml, use subject.properties
# default is: classpath:sources.xml
# e.g. file:/dir1/dir2/sources.xml
subject.sources.xml.location =
#########################################
## Configuration for source id: cmu
## Source configName: cmu
#########################################
subjectApi.source.cmu.id = cmu
# this is a friendly name for the source
subjectApi.source.cmu.name = cmu
# type is not used all that much. Can have multiple types, comma separate. Can be person, group, application
subjectApi.source.cmu.types = person
# the adapter class implements the interface: edu.internet2.middleware.subject.Source
# adapter class must extend: edu.internet2.middleware.subject.provider.BaseSourceAdapter
# edu.internet2.middleware.grouper.subj.GrouperJdbcSourceAdapter2 : if doing JDBC this should be used if possible. All subject da
ta in one table/view.
# edu.internet2.middleware.grouper.subj.GrouperJdbcSourceAdapter : oldest JDBC source. Put freeform queries in here
# edu.internet2.middleware.grouper.subj.GrouperJndiSourceAdapter : used for LDAP
subjectApi.source.cmu.adapterClass = edu.internet2.middleware.grouper.subj.GrouperJndiSourceAdapter
# e.g. com.sun.jndi.ldap.LdapCtxFactory
subjectApi.source.cmu.param.INITIAL_CONTEXT_FACTORY.value = com.sun.jndi.ldap.LdapCtxFactory
# e.g. ldap://localhost:389
subjectApi.source.cmu.param.PROVIDER_URL.value = ldap://ldap.andrew.cmu.edu:389/dc=cmu,dc=edu
# e.g. simple, none, sasl_mech
subjectApi.source.cmu.param.SECURITY_AUTHENTICATION.value = none
# ldap attribute which is the subject id. e.g. exampleEduRegID Each subject has one and only one subject id. Generally it is opa
que and permanent.
subjectApi.source.cmu.param.SubjectID_AttributeType.value = guid
# if the subject id should be changed to lower case after reading from datastore. true or false
subjectApi.source.cmu.param.SubjectID_formatToLowerCase.value = false
# attribute which is the subject name
subjectApi.source.cmu.param.Name_AttributeType.value = cn
# attribute which is the subject description
subjectApi.source.cmu.param.Description_AttributeType.value = nameLong
# when evaluating the virtual attribute EL expression, this variable can be used from this java class.
# subjectVirtualAttributeVariable_grouperUtilElSafe variable is the edu.internet2.middleware.grouper.util.GrouperUtilElSafe class.
Call static methods
subjectApi.source.cmu.param.subjectVirtualAttributeVariable_grouperUtilElSafe.value = edu.internet2.middleware.grouper.util.GrouperU
tilElSafe
# This virtual attribute index 0 is accessible via: subject.getAttributeValue("nameLong");
subjectApi.source.cmu.param.subjectVirtualAttribute_0_nameLong.value = ${grouperUtilElSafe.appendIfNotBlankString(grouperUtilElSafe.
defaultIfBlank(subject.getAttributeValue('cn'), ''), ' - ', grouperUtilElSafe.defaultIfBlank(subject.getAttributeValue('eduPersonSch
oolCollegeName'), ''))}
# the 1st sort attribute for lists on screen that are derived from member table (e.g. search for member in group)
# you can have up to 5 sort attributes
subjectApi.source.cmu.param.sortAttribute0.value = nameLong
# the 1st search attribute for lists on screen that are derived from member table (e.g. search for member in group)
# you can have up to 5 search attributes
subjectApi.source.cmu.param.searchAttribute0.value = nameLong
#searchSubject: find a subject by ID. ID is generally an opaque and permanent identifier, e.g. 12345678.
# Each subject has one and only on ID. Returns one result when searching for one ID.
# sql is the sql to search for the subject by id. %TERM% will be subsituted by the id searched for
subjectApi.source.cmu.search.searchSubject.param.filter.value = (& (guid=%TERM%) (objectclass=cmuPerson))
# Scope Values can be: OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE
subjectApi.source.cmu.search.searchSubject.param.scope.value = ONELEVEL_SCOPE
# base dn to search in
subjectApi.source.cmu.search.searchSubject.param.base.value = ou=person
#searchSubjectByIdentifier: find a subject by identifier. Identifier is anything that uniquely
# identifies the user, e.g. jsmith or jsmith@institution.edu.
# Subjects can have multiple identifiers. Note: it is nice to have if identifiers are unique
# even across sources. Returns one result when searching for one identifier.
# sql is the sql to search for the subject by identifier. %TERM% will be subsituted by the identifier searched for
subjectApi.source.cmu.search.searchSubjectByIdentifier.param.filter.value = (& (cmuAndrewId=%TERM%) (objectclass=cmuPerson))
# Scope Values can be: OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE
subjectApi.source.cmu.search.searchSubjectByIdentifier.param.scope.value = ONELEVEL_SCOPE
# base dn to search in
subjectApi.source.cmu.search.searchSubjectByIdentifier.param.base.value = ou=person
# search: find subjects by free form search. Returns multiple results.
# sql is the sql to search for the subject by free form search. %TERM% will be subsituted by the text searched for
subjectApi.source.cmu.search.search.param.filter.value = (& (|(|(cmuAndrewId=%TERM%)(cn=*%TERM%*))(guid=%TERM%))(objectclass=cmuPers
on))
# Scope Values can be: OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE
subjectApi.source.cmu.search.search.param.scope.value = ONELEVEL_SCOPE
# base dn to search in
subjectApi.source.cmu.search.search.param.base.value = ou=person
# attributes from ldap object to become subject attributes. comma separated
subjectApi.source.cmu.attributes = eduPersonSchoolCollegeName, sn, cmuStudentClass, givenName, mail
# internal attributes are used by grouper only not exposed to code that uses subjects. comma separated
subjectApi.source.cmu.internalAttributes = searchAttribute0
sources.xml (legacy)
Legacy format, kept for reference only. The pre-v2.3 sources.xml file was replaced by the subject.properties overlay above. The subject.sources.xml.location setting that loads it is still present in v4 but was removed in v6+. Use subject.properties for any new configuration; this section is a candidate for removal once v4 drops out of support.
Java test case
The corresponding test (LdapSubjectTest.java in the Grouper API) exercises the source — finding a subject by id and by identifier, and checking the name, description, and search/sort attributes:
Subject subject = SubjectFinder.findByIdAndSource("00000000-0000-1000-2F4C-0800207F02E6", "cmu", true);
assertEquals("Vincent Lun", subject.getName());
assertEquals("vlun@andrew.cmu.edu", subject.getAttributeValue("mail"));
assertEquals("Vincent Lun - Student Employment", subject.getDescription());
assertEquals("Vincent Lun - Student Employment", subject.getAttributeValue("nameLong"));
//check the search and sort attributes
Member member = MemberFinder.findBySubject(GrouperSession.startRootSession(), subject, true);
assertEquals("Vincent Lun - Student Employment", member.getSortString0());
assertEquals("vincent lun - student employment", member.getSearchString0());
Subject subject2 = SubjectFinder.findByIdentifierAndSource("vlun", "cmu", true);
assertEquals(subject.getId(), subject2.getId());
Set<Subject> subjects = SubjectFinder.findAll("Vincent Lun", "cmu");
//hmmm, will this be one? maybe
assertEquals(1, GrouperUtil.length(subjects));
assertEquals(subject.getId(), subjects.iterator().next().getId());