Skip to Content.
Sympa Menu

shibboleth-dev - Re: JNDI data connector and DN-based searches

Subject: Shibboleth Developers

List archive

Re: JNDI data connector and DN-based searches


Chronological Thread 
  • From: Brent Putman <>
  • To:
  • Subject: Re: JNDI data connector and DN-based searches
  • Date: Mon, 26 Sep 2005 10:34:22 -0400

In case anyone was actually interested enough to look at this...  I realized later that I had a bone-headed error with the provider URL stuff.  A fixed version of the full patch is attached.  It also now allows both the %PRINCIPAL% and %DN% macros to appear in both the search filter and JNDI provider URL, and also cleans up the way some of the errors and exceptions are reported.  Comments welcome.

Thanks,
Brent



Brent Putman wrote:
This question over on shib-users brought up again something that I have encountered in the past:  the general need within the resolver to use the value of a user's DN (obtained in an initial LDAP search) in a second search that has the first as a dependency.  I saw that in 1.3 in the JNDIDirectoryDataConnector, the DN value is now nicely being placed in the resolved attribute set (when using Sun's LDAP provider), but it still seems like you would have to write a custom connector to actually make use of it.  If I'm missing something there let me know...

What I want to be able to do is something more declarative in the resolver config example below -  a %DN% macro that gets expanded using the dn value available from a previous search (via a DataConnectorDependency).  And ideally, I want to be able to use it in either the search filter, *or* in the provider URL as the LDAP search base.


    <JNDIDirectoryDataConnector id="directory">
        <Search filter="uid=%PRINCIPAL%">
            <Controls searchScope="SUBTREE_SCOPE" returningObjects="false" />
        </Search>
        <Property name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory" />
        <Property name="java.naming.provider.url" value="ldap://directory.georgetown.edu/dc=georgetown,dc=edu" />
    </JNDIDirectoryDataConnector>


    <JNDIDirectoryDataConnector id="directory_groups" mergeMultipleResults="true">
        <DataConnectorDependency requires="directory"/>
        <Search filter="uniquemember=%DN%">
            <Controls searchScope="SUBTREE_SCOPE" returningObjects="false" />
        </Search>
        <Property name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory" />
        <Property name="java.naming.provider.url" value="ldap://directory.georgetown.edu/dc=georgetown,dc=edu" />
    </JNDIDirectoryDataConnector>

   
    <JNDIDirectoryDataConnector id="directory_roles" mergeMultipleResults="true">
        <DataConnectorDependency requires="directory"/>
        <Search filter="objectclass=organizationalRole">
            <Controls searchScope="SUBTREE_SCOPE" returningObjects="false" />
        </Search>
        <Property name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory" />
        <Property name="java.naming.provider.url" value="ldap://directory.georgetown.edu/%DN%" />
    </JNDIDirectoryDataConnector>


The "directory" connector is the traditional basic search using the user's authenticated principal name, and returns the user's "dn" attribute, among others.  The "directory_groups" example shows the typical case where the user's DN needs to be used as the input to the search filter, for example when the directory contains GroupofNames or GroupofUniqueNames objects, containing member or uniqueMember attributes with DN values.  I think this is a very common use case.  The "directory_roles" example is the less common (but nonetheless extant and valid) case where the user's DN is not a leaf, but is a container holding subordinate objects specific to that user (ala Roman's example).  Here you need to be able to insert the  user's DN into the LDAP search base.

So, with that in mind, I have written the attached patch for JNDIDirectoryDataConnector which implements the above basic functionality.  The search filter case is pretty easy.  The provider url case is a little more complicated, and means having to skip the fail-fast test on connector initialization (or maybe using a known fixed search base to test with, but that might not be portable across directory servers).  In order for the %DN% macro expansion to work, you must have a DataConnectorDependency declared which has resolved a "dn" attribute.  I have tested it and it does work for both of the above configs, but would consider it a work-in-progress... I'm sure I've missed some edge cases, etc.

So I throw this out there for consideration, or at least for discussion.  Perhaps others won't consider the use cases to be common enough to warrant adding this functionality to the base connector.  If that is the case, then I could perhaps rework this into a custom connector that extends the base JNDI connector.  Mainly I just wanted to see what others think.

Thanks,
Brent

Index: JNDIDirectoryDataConnector.java
===================================================================
RCS file:
/home/cvs/shibboleth/shibboleth/java/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/JNDIDirectoryDataConnector.java,v
retrieving revision 1.18
diff -u -r1.18 JNDIDirectoryDataConnector.java
--- JNDIDirectoryDataConnector.java 21 Aug 2005 11:42:03 -0000 1.18
+++ JNDIDirectoryDataConnector.java 26 Sep 2005 07:35:18 -0000
@@ -70,6 +70,7 @@

private static Logger log =
Logger.getLogger(JNDIDirectoryDataConnector.class.getName());
protected String searchFilter;
+ protected String providerURL;
protected Properties properties;
protected SearchControls controls;
protected boolean mergeMultiResults = false;
@@ -140,6 +141,8 @@
throw new ResolutionPlugInException("Property
is malformed.");
} else {
properties.setProperty(propName, propValue);
+ if
(propName.equals("java.naming.provider.url"))
+ providerURL = propValue;
}
}

@@ -148,8 +151,12 @@
try {
if (!startTls) {
try {
- log.debug("Attempting to connect to
JNDI directory source as a sanity check.");
- context = initConnection();
+ if ( !
providerURL.contains("%PRINCIPAL%") && ! providerURL.contains("%DN%")) {
+ log.debug("Attempting to
connect to JNDI directory source as a sanity check.");
+ context = initConnection();
+ } else {
+ log.debug("Bypassing JNDI
fail-fast test because provider URL contains a '%PRINCIPAL%' or '%DN%'
macro");
+ }
} catch (IOException ioe) {
log.error("Failed to startup
directory context: " + ioe);
throw new
ResolutionPlugInException("Failed to startup directory context.");
@@ -185,8 +192,12 @@
sslc.init(new
KeyManager[]{keyManager}, null, new SecureRandom());
sslsf = sslc.getSocketFactory();

- log.debug("Attempting to connect to
JNDI directory source as a sanity check.");
- initConnection();
+ if ( !
providerURL.contains("%PRINCIPAL%") && ! providerURL.contains("%DN%")) {
+ log.debug("Attempting to
connect to JNDI directory source as a sanity check.");
+ initConnection();
+ } else {
+ log.debug("Bypassing JNDI
fail-fast test because provider URL contains a '%PRINCIPAL%' or '%DN%'
macro");
+ }
} catch (GeneralSecurityException gse) {
log.error("Failed to startup
directory context. Error creating SSL socket: " + gse);
throw new
ResolutionPlugInException("Failed to startup directory context.");
@@ -299,6 +310,74 @@
InitialDirContext context = null;
NamingEnumeration nEnumeration = null;
String populatedSearch =
searchFilter.replaceAll("%PRINCIPAL%", principal.getName());
+
+ String populatedProviderURL = null;
+ if (providerURL.contains("%PRINCIPAL%")) {
+ populatedProviderURL =
providerURL.replaceAll("%PRINCIPAL%", principal.getName());
+ // TODO may need to encode/escape this string
further, if it has spaces, etc
+ properties.setProperty("java.naming.provider.url",
populatedProviderURL);
+ log.debug("JNDI provider URL macro substitution on
'%PRINCIPAL%' for principal ("
+ + principal.getName() + ") resulted
in '" + populatedProviderURL + "'");
+ } else {
+ populatedProviderURL = providerURL;
+ }
+
+ if (populatedSearch.contains("%DN%") ||
populatedProviderURL.contains("%DN%") ) {
+ Iterator connectorDependIt =
connectorDependencyIds.iterator();
+ Attribute attr = null;
+ boolean foundDN = false;
+ while (connectorDependIt.hasNext()) {
+ Attributes attrs =
depends.getConnectorResolution((String) connectorDependIt.next());
+ if (attrs != null) {
+ attr = attrs.get("dn");
+ // TODO: Break out when find the
first dn attribute - there can be only one...
+ // or maybe should loop over
them all.. or throw an error if multi ???
+ if (attr != null) {
+ foundDN = true;
+ break;
+ }
+ }
+ }
+
+ if (!foundDN) {
+ log.error("Unable to resolve a 'dn' attribute
value from data connector dependencies " +
+ "for '%DN%' for principal ("
+ principal.getName() + ")" );
+ throw new ResolutionPlugInException("Unable
to resolve a 'dn' value for '%DN%'.");
+ }
+
+ if (attr != null) {
+ // There should only be one value for dn
+ String dn = null;
+ try {
+ dn = (String) attr.get();
+ }
+ catch (NamingException e) {
+ log.error("An error occurred while
obtaining 'dn' attribute dependency data for principal ("
+ + principal.getName()
+ ") :" + e.getMessage());
+ throw new
ResolutionPlugInException("Problem obtaining 'dn' attribute value from
resolved data connector dependency");
+ }
+ if (!dn.equals("")) {
+ populatedSearch =
populatedSearch.replaceAll("%DN%", dn);
+ if
(populatedProviderURL.contains("%DN%")) {
+ populatedProviderURL =
populatedProviderURL.replaceAll("%DN%", dn);
+ // TODO may need to
encode/escape this string further, if it has spaces, etc
+
properties.setProperty("java.naming.provider.url", populatedProviderURL);
+ log.debug("JNDI provider URL
macro substitution on '%DN%' for principal ("
+ + principal.getName()
+ ") resulted in '" + populatedProviderURL + "'");
+ }
+ } else {
+ log.error("Found an empty 'dn'
attribute value for '%DN%' for principal ("
+ + principal.getName()
+ ")");
+ throw new
ResolutionPlugInException("Found an empty 'dn' value for '%DN%'");
+ }
+ } else {
+ log.error("Found a null 'dn' attribute value
for '%DN%' for principal ("
+ + principal.getName() + ")" );
+ throw new ResolutionPlugInException("Found a
null 'dn' value for '%DN%'.");
+ }
+ }
+
+
try {
try {
context = initConnection();



Archive powered by MHonArc 2.6.16.

Top of Page