This chapter explains how to use Java extensions to the standard directory APIs to perform many of the operations introduced in Chapter 3. The chapter presents use cases. The Oracle extensions to the standard APIs are documented in full in Oracle Fusion Middleware Java API Reference for Oracle Internet Directory.
The chapter contains the following topics:
Sample code is available at this URL:
http://www.oracle.com/technology/sample_code/
Look for the Oracle Identity Management link under Sample Applications–Oracle Application Server.
The Java extensions are installed along with the standard Java APIs when the LDAP client is installed. The APIs and their extensions are found at $ORACLE_HOME/jlib/ldapjclnt10.jar
.
In Java, LDAP entities—users, groups, realms, and applications—are modeled as Java objects instead of as handles. This modeling is done in the oracle.java.util
package. All other utility functionality is modeled either as individual objects—as, for example, GUID
—or as static member functions of a utility class.
For example, to authenticate a user, an application must follow these steps:
Create oracle.ldap.util.User
object, given the user DN.
Create a DirContext
JNDI object with all of the required properties, or get one from a pool of DirContext
objects.
Invoke the User.authenticateUser
method, passing in a reference to the DirContext
object and the user credentials.
If the DirContext
object was retrieved from a pool of existing DirContext
objects, return it to that pool.
Unlike their C and PL/SQL counterparts, Java programmers do not have to explicitly free objects. The Java garbage collection mechanism performs this task.
Many of the methods in the user, subscriber, and group classes return a PropertySetCollection
object. The object represents a collection of one or more LDAP entries. Each of these entries is represented by a PropertySet
object, identified by a DN. A property set can contain attributes, each represented as a property. A property is a collection of one or more values for the particular attribute it represents. An example of the use of these classes follows:
PropertySetCollection psc = Util.getGroupMembership( ctx, myuser, null, true ); // for loop to go through each PropertySet for (int i = 0; i < psc.size(); i++ ) { PropertySet ps = psc.getPropertySet(i); // Print the DN of each PropertySet System.out.println("dn: " + ps .getDN()); // Get the values for the "objectclass" Property Property objectclass = ps.getProperty( "objectclass" ); // for loop to go through each value of Property "objectclass" for (int j = 0; j< objectclass.size(); j++) { // Print each "objectclass" value System.out.println("objectclass: " + objectclass.getValue(j)); } }
The entity myuser
is a user object. The psc
object contains all the nested groups that myuser belongs to. The code loops through the resulting entries and prints out all the object class values of each entry.
All user-related functionality is abstracted in a Java class called oracle.ldap.util.User
. The process works like this:
Construct a oracle.ldap.util.User
object based on a DN, GUID, or simple name.
Invoke User.authenticateUser(DirContext, int, Object)
to authenticate the user if necessary.
Invoke User.getProperties(DirContext)
to get the attributes of the user entry.
Invoke User.getExtendedProperties(DirContext, int, String[])
to get the extended properties of the user. int
is either shared or application-specific. String[]
is the object that represents the type of property desired. If String[]
is null, all properties in a given category are retrieved.
Invoke PropertySetCollection.getProperties(int)
to get the metadata required to parse the properties returned in step 4.
Parse the extended properties and continue with application-specific logic. This parsing is also performed by application-specific logic.
User authentication is a common LDAP operation that compares the credentials that a user provides at login with the user's credentials in the directory. Oracle Internet Directory supports the following:
Arbitrary attributes can be used during authentication
Appropriate password policy exceptions are returned by the authentication method. Note, however, that the password policy applies only to the userpassword
attribute.
The following code fragment shows how the API is used to authenticate a user:
// User user1 - is a valid User Object try { user1.authenticateUser(ctx, User.CREDTYPE_PASSWD, "welcome"); // or // user1.authenticateUser(ctx, <any attribute>, <attribute value>); } catch (UtilException ue) { // Handle the password policy error accordingly if (ue instanceof PasswordExpiredException) // do something else if (ue instanceof GraceLoginException) // do something }
The subscriber class uses the createUser()
method to programmatically create users. The object classes required by a user entry are configurable through Oracle Delegated Administration Services. The createUser()
method assumes that the client understands the requirement and supplies the values for the mandatory attributes during user creation. If the programmer does not supply the required information the server returns an error.
The following snippet of sample code demonstrates the usage.
// Subscriber sub is a valid Subscriber object // DirContext ctx is a valid DirContext // Create ModPropertySet object to define all the attributes and their values. ModPropertySet mps = new ModPropertySet(); mps.addProperty(LDIF.ATTRIBUTE_CHANGE_TYPE_ADD,"cn", "Anika"); mps.addProperty(LDIF.ATTRIBUTE_CHANGE_TYPE_ADD,"sn", "Anika"); mps.addProperty(LDIF.ATTRIBUTE_CHANGE_TYPE_ADD,"mail", "[email protected]"); // Create user by specifying the nickname and the ModPropertySet just defined User newUser = sub.createUser( ctx, mps, true); // Print the newly created user DN System.out.println( newUser.getDN(ctx) ); // Perform other operations with this new user
The subscriber class offers the getUser()
method to replace the public constructors of the User class. A user object is returned based on the specified information.
The following is a piece of sample code demonstrating the usage:
// DirContext ctx is contains a valid directory connection with sufficient privilege to perform the operations // Creating RootOracleContext object RootOracleContext roc = new RootOracleContext(ctx); // Obtain a Subscriber object representing the default subscriber Subscriber sub = roc.getSubscriber(ctx, Util.IDTYPE_DEFAULT, null, null); // Obtain a User object representing the user whose nickname is "Anika" User user1 = sub.getUser(ctx, Util.IDTYPE_SIMPLE, "Anika", null); // Do work with this user The getUser() method can retrieve users based on DN, GUID and simple name. A getUsers() method is also available to perform a filtered search to return more than one user at a time. The returned object is an array of User objects. For example, // Obtain an array of User object where the user's nickname starts with "Ani" User[] userArr = sub.getUsers(ctx, Util.IDTYPE_SIMPLE, "Ani", null); // Do work with the User array
This section describes how the Java API can be used to retrieve objects in identity management realms.
The RootOracleContext
class represents the root Oracle Context. Much of the information needed for identity management realm creation is stored within the root Oracle Context. The RootOracleContext
class offers the getSubscriber()
method. It replaces the public constructors of the subscriber class and returns an identity management realm object based on the specified information.
The following is a piece of sample code demonstrating the usage:
// DirContext ctx contains a valid directory // connection with sufficient privilege to perform the // operations // Creating RootOracleContext object RootOracleContext roc = new RootOracleContext(ctx); // Obtain a Subscriber object representing the // Subscriber with simple name "Oracle" Subscriber sub = roc.getSubscriber(ctx, Util.IDTYPE_SIMPLE, "Oracle", null); // Do work with the Subscriber object
The following example shows how to find a user's login name when you have the simple name, GUID, or DN. The Oracle Single Sign-On login name is also referred to as nickname.
There are two parts to this example:
Determine which attribute is used to store the nickname in this realm.
Retrieve the User object and determine the value of the nickname attribute.
import javax.naming.*; import javax.naming.directory.*; import javax.naming.ldap.*; import oracle.ldap.util.jndi.*; import oracle.ldap.util.*; import java.io.*; public class NickNameSearch { public static void main(String[] args) throws Exception { InitialLdapContext ctx = ConnectionUtil.getDefaultDirCtx( args[0], args[1], args[2],args[3]); RootOracleContext roc=new RootOracleContext(ctx); Subscriber sub = null; sub = roc.getSubscriber(ctx, Util.IDTYPE_DEFAULT, null, null) ; PropertySetCollection psc = sub.getProperties(ctx, Subscriber.USER_NAMING_PROPERTIES, null); String nickNameAttribute = null; try { nickNameAttribute = (String) psc.getPropertySet(0).getProperty(Subscriber.USER_NAMING_ATTR_SIMPLE).getValue(0); } catch (Exception e) { // unable to retrieve the attribute name System.exit(0); } System.out.println("Nickname attribute: " + nickNameAttribute); // Retrieve user using simple name, guid or DN User user = sub.getUser(ctx, Util.IDTYPE_SIMPLE,"orcladmin", null); System.out.println("user DN: " + user.getDN(ctx)); // Retrieve nickname value using User object psc = user.getProperties(ctx, new String[]{ nickNameAttribute }); String nickName = null; try { nickName = (String) psc.getPropertySet(0).getProperty(nickNameAttribute).getValue(0); } catch (Exception e) { // unable to retrieve the attribute value System.exit(0); } System.out.println("Nickname : " + nickName); } }
A new Java class, the public class, has been introduced:
public class oracle.ldap.util.discovery.DiscoveryHelper
This class provides a method for discovering specific information from the specified source.
Table 5-1 Methods for Directory Server Discovery
Method | Description |
---|---|
discover |
Discovers the specific information from a given source |
setProperty |
Sets the properties required for discovery |
getProperty |
Accesses the value of properties |
Two new methods are added to the existing Java class oracle.ldap.util.jndi.ConnectionUtil
:
getDefaultDirCtx
: This overloaded function determines the host name and port information of non-SSL ldap servers by making an internal call to oracle.ldap.util.discovery.DiscoveryHelper.discover()
.
getSSLDirCtx
: This overloaded function determines the host name and port information of SSL ldap servers by making an internal call to oracle.ldap.util.discovery.DiscoveryHelper.discover()
.
The following is a sample Java program for directory server discovery:
import java.util.*; import java.lang.*; import oracle.ldap.util.discovery.*; import oracle.ldap.util.jndi.*; public class dsdtest { public static void main(String s[]) throws Exception { HashMap reshdl = new HashMap(); String result = new String(); Object resultObj = new Object(); DiscoveryHelper disco = new DiscoveryHelper(DiscoveryHelper.DNS_DISCOVER); // Set the property for the DNS_DN disco.setProperty(DiscoveryHelper.DNS_DN,"dc=us,dc=fiction,dc=com") ; // Set the property for the DNS_DISCOVER_METHOD disco.setProperty(DiscoveryHelper.DNS_DISCOVER_METHOD, DiscoveryHelper.USE_INPUT_DN_METHOD); // Set the property for the SSLMODE disco.setProperty(DiscoveryHelper.SSLMODE,"0"); // Call the discover method int res=disco.discover(reshdl); if (res!=0) System.out.println("Error Code returned by the discover method is :"+res) ; // Print the results printReshdl(reshdl); } public static void printReshdl(HashMap reshdl) { ArrayList result = (ArrayList)reshdl.get(DiscoveryHelper.DIR_SERVERS); if (result != null) { if (result.size() == 0) return; System.out.println("The hostnames are :-"); for (int i = 0; i< result.size();i++) { String host = (String)result.get(i); System.out.println((i+1)+". '"+host+"'"); } } } }
When using JNDI to create a SASL connection, you must set these javax.naming.Context
properties:
Context.SECURITY_AUTHENTICATION = "DIGEST-MD5"
Context.SECURITY_PRINCIPAL
The latter sets the principal name. This name is a server-specific format. It can be either of the following:
The DN—that is, dn:
—followed by the fully qualified DN of the entity being authenticated
The string u:
followed by the user identifier.
The Oracle directory server accepts just a fully qualified DN such as cn=user,ou=my department,o=my company
.
Note:
The SASL DN must be normalized before it is passed to the API that calls the SASL bind. To generate SASL verifiers, Oracle Internet Directory supports only normalized DNs.The following code provides an example of Java LDAP/JNDI using SASL Digest-MD5.
/* $Header: LdapSasl.java 27-oct-2005.11:26:59 qdinh Exp $ */ /* Copyright (c) 2003, 2005, Oracle. All rights reserved. */ /* DESCRIPTION <short description of component this file declares/defines> PRIVATE CLASSES <list of private classes defined - with one-line descriptions> NOTES <other useful comments, qualifications, and so on.> MODIFIED (MM/DD/YY) qdinh 04/23/03 - Creation */ /** * @version $Header: LdapSasl.java 27-oct-2005.11:26:59 qdinh Exp $ * @author qdinh * @since release specific (what release of product did this appear in) */ package oracle.ldap.util.jndi; import javax.naming.*; import javax.naming.directory.*; import javax.naming.ldap.*; import oracle.ldap.util.jndi.*; import oracle.ldap.util.*; import java.lang.*; import java.util.*; public class LdapSasl { public static void main( String[] args) throws Exception { int numofargs; numofargs = args.length; Hashtable hashtable = new Hashtable(); // Look through System Properties for Context Factory if it is available // then set the CONTEXT factory only if it has not been set // in the environment - // set default to com.sun.jndi.ldap.LdapCtxFactory hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // possible valid arguments // args[0] - hostname // args[1] - port number // args[2] - Entry DN // args[3] - Entry Password // args[4] - QoP [ auth | auth-int | auth-conf ] // args[5] - SASL Realm // args[6] - Cipher Choice // If QoP == "auth-conf" then args[6] cipher choice can be // - des // - 3des // - rc4 // - rc4-56 // - rc4-40 hashtable.put(Context.PROVIDER_URL, "ldap://"+args[0]+":"+args[1]); hashtable.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5"); System.out.println("hash put security dn: " + args[2]); hashtable.put(Context.SECURITY_PRINCIPAL, args[2] ); hashtable.put(Context.SECURITY_CREDENTIALS, args[3] ); // For Quality of Protection modes // 1. Authentication and Data Integrity Mode - "auth-int" // 2. Authentication and Data Confidentiality Mode "auth-conf" // // hashtable.put("javax.security.sasl.qop",args[4]); hashtable.put("javax.naming.security.sasl.realm", args[5]); // Setup Quality of Protection // // System.out.println("hash sasl.qop: " + args[4]); hashtable.put("javax.security.sasl.qop",args[4]); if (numofargs > 4) { if (args[4].equalsIgnoreCase("AUTH-CONF")) { // Setup a cipher choice only if QoP == "auth-conf" String strength = "high"; String cipher = new String(args[6]); if (cipher.compareToIgnoreCase("rc4-40") == 0) strength = "low"; else if (cipher.compareToIgnoreCase("rc4-56") == 0 || cipher.compareToIgnoreCase("des")== 0 ) strength = "medium"; else if (cipher.compareToIgnoreCase("3des") == 0 || cipher.compareToIgnoreCase("rc4") == 0) strength = "high"; // setup cipher choice System.out.println("hash sasl.strength:"+strength); hashtable.put("javax.security.sasl.strength",strength); } // set maxbuffer length if necessary if (numofargs > 7 && !"".equals(args[6])) hashtable.put("javax.security.sasl.maxbuf", args[5].toString()); } // Enable Debug -- // hashtable.put("com.sun.jndi.ldap.trace.ber", System.err); LdapContext ctx = new InitialLdapContext(hashtable,null); // At this stage - SASL Digest -MD5 has been successfully System.out.println("sasl bind successful"); // Ldap Search Scope Options // // - Search base - OBJECT_SCOPE // - One Level - ONELEVEL_SCOPE // - Sub Tree - SUBTREE_SCOPE // // Doing an LDAP Search PropertySetCollection psc = Util.ldapSearch(ctx,"o=oracle,dc=com","objectclass=*",SearchControls.OBJECT_SCOPE, new String[] {"*"}); // Print out the serach result Util.printResults(psc); System.exit(0); } }