Oracle® Fusion Middleware Administrator's Guide for Oracle Virtual Directory 11g Release 1 (11.1.1) Part Number E10046-03 |
|
|
View PDF |
This chapter explains how to customize Oracle Virtual Directory and contains the following topics:
Oracle Virtual Directory includes localized translations for the Oracle Directory Services Manager interface in the following languages:
French
Italian
German
Spanish
Brazilian Portuguese
Japanese
Traditional Chinese
Simplified Chinese
Korean
You can set the language for the Oracle Directory Services Manager interface using your web browser's language settings. Refer to your web browser's documentation for specific information on setting languages.
Notes:
Only the superuser (usually cn=orcladmin
) can log in to Oracle Directory Services Manager.
The user name of the superuser used to log in to Oracle Directory Services Manager must be comprised of only ASCII characters. You cannot log in to Oracle Directory Services Manager using a user name of the superuser that contains non-ASCII characters.
Oracle Virtual Directory supports the ability to create custom adapters using plug-ins that can connect to almost any data source with a defined API. For example, you can use custom adapters to abstract information available through web services. A custom adapter is an adapter that has no functionality itself—it is a place holder where adapter level plug-ins can be configured to implement its functions instead. By default, Custom Adapters do not map to any data source. Plug-ins, such as the Diameter plug-in, that are added to Custom Adapters on the Plug-In tab in Oracle Directory Services Manager provide data to Custom Adapters. Typically, Custom Adapters are written by customers that must connect Oracle Virtual Directory to non-LDAP or non-database services, such as Web Services.
This topic contains the following sections:
Perform the following steps to create a Custom Adapter using Oracle Directory Services Manager:
Log in to Oracle Directory Services Manager.
Select Adapter from the task selection bar. The Adapter navigation tree appears.
Click the Create Adapter button. The New Adapter Wizard appears.
Perform the following steps to define the Type of adapter:
Select Custom from the Adapter Type list.
Enter a unique name for the Custom Adapter in the Adapter Name field. The adapter name value is used in other configuration fields that need to reference the adapter.
Select the Default template from the Adapter Template list.
Click Next. The Settings screen appears.
Enter a valid base DN (in DN format) in the Adapter Suffix/Namespace field. This field defines the root DN that the adapter provides information for. The DN defined, and the child entries below it, comprise the adapter's namespace. The value you enter in the Adapter Suffix field will be the base DN value that returned entries will have. For example, if you enter dc=mydomain,dc=com in the Adapter Suffix/Namespace field, all entries will end with dc=mydomain,dc=com.
Click Next. A summary of the Custom Adapter settings appears. Review the settings and click Finish to create the Custom Adapter. The Custom Adapter appears in the Adapter tree.
After you create the Custom Adapter you can configure it using the procedures in Configuring Custom Adapters.
This section describes how to configure Custom Adapter settings, including:
After you create the Custom Adapter you can configure the general settings for the adapter by clicking the adapter name in the Adapter tree, clicking the General tab, setting values for the following fields, and clicking Apply:
This field defines the root DN that the adapter provides information for. The DN defined, and the child entries below it, comprise the adapter's namespace. The value you enter in this field will be the base DN value that returned entries will have. For example, if you enter dc=mydomain,dc=com in the field, all entries will end with dc=mydomain,dc=com.
An adapter can be configured as active (enabled) or inactive (disabled). An adapter configured as inactive will not start during a server restart or an attempted adapter start. Use the inactive setting to keep old configurations available or in stand-by without having to delete them from the configuration. The default setting is active.
After you create the adapter you can configure routing for the adapter by clicking the adapter name in the Adapter tree, clicking the Routing tab, and referring to "Understanding Routing Settings".
Note:
Enable the Bind Support routing setting when defining Custom Adapters that may or may not support a bind operation.After you create the adapter you can apply Plug-ins and Mappings to the adapter by clicking the adapter name in the Adapter tree, clicking the Plug-Ins tab, and referring to "Managing Adapter Plug-ins" and "Applying Mappings to Adapters".
This topic explains how to develop custom Java plug-ins for Oracle Virtual Directory and contains the following section:
Oracle Virtual Directory allows you to create and deploy custom Java plug-ins that can process and manipulate LDAP operations as they pass through the Oracle Virtual Directory. Plug-ins can be positioned at either a global level, where they see and affect all requests, or at an adapter level, where they see and affect only requests for a particular adapter. You can also create and deploy plug-ins to run on particular operations and for certain namespaces.
Note:
If you rename attributes using custom Java plug-ins, Oracle Virtual Directory supports search on the renamed attribute/value only if the custom code overrides the incoming filter object, as is in the DB_Groups Mapping.Each Oracle Virtual Directory plug-in has a specific implementation point, as listed in Table 18-1:
Table 18-1 Plug-In Implementation Points
Implementation Point | Description |
---|---|
Configuration |
Plug-in configuration data. The custom portion of the configuration consists of name and value pairs of initialization parameters |
Startup / Shutdown |
The |
Availability |
The |
Operations |
The various operational methods that will be called. |
This chapter demonstrates how to create a custom plug-in by explaining the implementation points listed in Table 18-1. The chapter provides information for a fictitious example plug-in called the Bad Password Count plug-in which would detect if a bind operation has failed or succeeded. If the operation succeeded, then the count would be cleared and if the bind fails, then the count would increase. The fictitious Bad Password Count plug-in would also make sure that the bad password count cannot be changed from outside the directory.
Note:
The Bad Password Count plug-in described in this chapter is a fictitious example used to demonstrate how Oracle Virtual Directory plug-ins and its chain system operate. Oracle Virtual Directory does not include a Bad Password Count plug-in, though it could support one if you created it.Oracle Virtual Directory plug-ins follow an implementation based on the Java Servlet 2.3 Filter model where a single method is used to handle the pre-operation and post-operation, and to determine if an operation should continue. Multiple plug-ins are combined to form a chain of plug-ins. To demonstrate this chain implementation, consider the following situation where the fictitious example Bad Password Count plug-in determines if the bad password count attribute should be added to an entry being added to the directory.
You have the ability to manipulate the request when the add method is called, which allows you to manipulate the passed-in attributes and their values (for example, to change objectclass value inetOrgPerson
to user
if you were masking ActiveDirectory
as a standard LDAP directory) or to handle the storage of the data into your non-directory or database system (such as in a custom adapter). If you want to allow the virtual directory to have a chance to further process the request through other plug-ins, you would call the chain.nextAdd
method. Most plug-in methods have corresponding chain.next<XXX>
methods. If you do not want to allow further processing of the request by plug-ins, you can omit the chain.next<XXX>
call.
Note:
The adapter type is irrelevant to the plug-ins, as plug-ins can be added to any type of adapter.Before you can build a custom plug-in, you must decide whether to implement the com.octetstring.vde.chain.Plugin
interface or extend the com.octetstring.vde.chain.BasePlugin
class. The BasePlugin
class is a convenience that allows a plug-in developer to only implement the methods for operations to be handled by the plug-in. The example plug-in in this chapter extends the BasePlugin class to simplify the implementation.
The sections in this topic describe the Oracle Virtual Directory plug-in implementation points, including:
Configuration is the first plug-in implementation point. Plug-ins are configured using a set of simple name and value pairs provided by the Oracle Virtual Directory configuration system. The pairs are provided to the plug-in developer through the params
argument to the init
method of the plug-in. The example plug-in in this chapter includes the following configuration options:
countAttribute
: An attribute that will be attached to all user entries that will store the bad password count.
addOnCreate
: Boolean value set to true if the plug-in will add this attribute when a user is created.
objectClassForAdd
: The object classes that represent users who will have the attribute added to.
ignoreOnModify
: Boolean value, set to true if modify requests on the countAttribute
should be ignored.
The configuration options listed above will be picked-up at the life cycle methods, which is the second implementation point. The init
method is called on the initialization of the plug-in at server startup and the destroy method is called when the plug-in is being shutdown. Example 18-1 shows an example init
method:
Example 18-1 Example init Method
/** * Passes initialization information to the Plug-in * * @param initParams * Hashmap of key/value pairs specified in initial config * @param name * The name specified in the config for this Plug-in */ public void init(PluginInit initParams, String name) throws ChainException { //the countAttribute parameter is required if (!initParams.containsKey(BadPasswordCount.CONFIG_COUNT_ATTRIBUTE)) { throw new ChainException(name + ": The " + BadPasswordCount.CONFIG_COUNT_ATTRIBUTE + " attribute is required"); } this.countAttribute = new DirectoryString(initParams .get(BadPasswordCount.CONFIG_COUNT_ATTRIBUTE)); this.attribType = SchemaChecker.getInstance().getAttributeType( this.countAttribute); //determine if add on create this.addOnCreate = initParams .containsKey(BadPasswordCount.CONFIG_ADD_ON_CREATE) && initParams.get(BadPasswordCount.CONFIG_ADD_ON_CREATE) .equalsIgnoreCase("true"); if (this.addOnCreate) { if (this.addOnCreate && !initParams .containsKey(BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD)) { throw new ChainException(name + ": When adding count attribute, the parameter " + BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD + " is required"); } String[] objectClasses = initParams .getVals(BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD); this.objectClasses = new HashSet(); for (int i = 0, m = objectClasses.length; i < m; i++) { this.objectClasses.add(new DirectoryString(objectClasses[i])); } } else { this.addOnCreate = false; } logger.info("Adding on create : " + this.addOnCreate); //determine if the modify operation should be ignored this.ignoreModify = initParams .containsKey(BadPasswordCount.CONFIG_IGNORE_MODIFY) && initParams.get(BadPasswordCount.CONFIG_IGNORE_MODIFY) .equalsIgnoreCase("true");
The method in Example 18-1 checks the initialization parameters to setup the plug-in. If there is not enough configuration information, then the plug-in throws an exception, causing the plug-in to not be configured for operational use by the server. The destroy method is not required to be implemented unless there is a need to release any connections or shutdown any services.
The available implementation point follows the configuration and startup and shutdown implementation points. The available method is called before each plug-in can be called for a particular LDAP operation. If the available method returns as true, then the plug-in is executed. In Example 18-2, the available method checks for the existence of the ignoreOnModify
option in the Request
object. If it is defined, then the plug-in will be skipped. Similarly, if the addonCreate
option is set to false, the plug-in will be skipped.
Example 18-2 Example Method Checking for ignoreOnModify Option
/** * Determines if a plugin is available for the current chain * * @param chain * @param base * @return True or False if available for a particular chain & base */ public boolean available(Chain chain, DirectoryString base) { if (chain.getOperationType() == Chain.ADD_OP && !this.addOnCreate) { return false; } else if (chain.getOperationType() == Chain.MOD_OP && this.ignoreOnModify) { return false; } else { return true; } }
If the available method returns as true, the operation portion of the request will be executed.
The final implementation point is operation implementations. Consider the following code implementation of a bind operation in Example 18-3:
Example 18-3 Example Bind Operation Implementation
/** * Moves through the "bind" operation's chain * * @param chain * The current chain * @param dn * The DN for the user * @param password * The user's password * @param result * The result of the bind */ public void bind(Chain chain, Credentials creds, DirectoryString dn, BinarySyntax password, Bool result) throws DirectoryException, ChainException { // Pre-event processing // calls the next plug-in in the chain (or comment out if a handler) try { chain.nextBind(creds, dn, password, result); } catch (DirectoryException e) { throw e; } // Post-event processing if (result.booleanValue()) { // success, reset count setPasswordCount(chain, creds, dn, 0); } else { Vector searchAttributes = new Vector(); searchAttributes.add(this.countAttribute); ChainVector results = new ChainVector(); chain.getVSI().get(chain.getRequest(), creds, dn, new Int8((byte) 0), ParseFilter.parse("(objectClass=*)"), new Bool(false), searchAttributes, results); if (results.size() > 0) { EntrySet es = (EntrySet) results.get(0); Entry entry = es.getNext(); Vector values = entry.get(this.countAttribute); Syntax value = (Syntax) values.get(0); IntegerSyntax is = new IntegerSyntax(value.getValue()); setPasswordCount(chain, creds, dn, ((int) is.getLongValue()) + 1); } else { setPasswordCount(chain, creds, dn, 1); } } } private void setPasswordCount(Chain chain, Credentials creds, DirectoryString dn, int count) throws DirectoryException, ChainException { Vector values = new Vector(); values.add(new IntegerSyntax(count)); EntryChange modify = new EntryChange(EntryChange.MOD_REPLACE, this.countAttribute, values); Vector changes = new Vector(); changes.add(modify); chain.getVSI().modify(chain.getRequest(), creds, dn, changes); }
The method in Example 18-3 shows an example where password failure counts are being maintained within the directory as a form of password policy. Notice that the method does not perform any pre-processing of the operation, nor does it attempt to take over the bind operation. The plug-ins bind method immediately calls the chain.nextBind
method and waits for the bind to complete before moving forward with its own logic. Once the bind is complete, that is, control is returned from chain.nextBind
, the plug-in checks to see if the bind was successful or not. If the bind was successful, the plug-in sets the failure count attribute to zero for the user. If the bind failed, then the current failure count is retrieved and an increased value is set.
The bind method uses the Virtual Services Interface (VSI) to modify records for the binding user. You can use the VSI interface throughout Oracle Virtual Directory as a consistent way to access directory information regardless of whether a plug-in is deployed globally or within the context of an adapter. VSI does this by always calling into Oracle Virtual Directory by starting with the next plug-in in the chain after the current plug-in. For example, if there is a mapper before the plug-in, and a cache after the plug-in, then the call to VSI will go only through the cache.
Because the plug-in is now logically in charge of maintaining the bind failure count, the plug-in modify method must be implemented so that any attempt by an LDAP client to modify the count is blocked. The plug-in modify method in Example 18-4 is implemented to throw an exception if the count attribute is included in the modify change list.
Example 18-4 Example modify Method
/** * Moves through the "modify" operation's chain * * @param chain * The current chain * @param creds * The currnet user's credentials * @param name * The name of the object being modified * @param changeEntries * The group of EntryChange Objects */ public void modify(Chain chain, Credentials creds, DirectoryString name, Vector changeEntries) throws DirectoryException, ChainException { Iterator it = changeEntries.iterator(); while (it.hasNext()) { EntryChange ec = (EntryChange) it.next(); if (ec.getAttr().equals(this.countAttribute)) { throw new DirectoryException(LDAPResult.CONSTRAINT_VIOLATION .intValue(), "Can not modify password count attribute"); } } chain.nextModify(creds, name, changeEntries); }
A DirectoryException
is thrown with both a status code and a message. If this exception is not caught by another plug-in, then both the message and the code will make it back to the client. For this example, you do not need to check and see if the ignoreOnModify
has been configured because you have delegated that decision to the available method. If it was set, the plug-in modify method in Example 18-4 would not have been called.
Searches are different then the other operations because there is not one, but three methods that may be implemented. The first method, get, acts as the other operation methods do and allows for the plug-in developer to pre-process the search request and post process the returns. While you could process each Entry returned, it would be very inefficient in terms of memory utilization to do so. Processing results in the get method means that all results must be obtained in memory before they can be returned to the client. For this reason there is a postSearchEntry
method, which is executed for every Entry that will be returned to the client where attributes can be changed, added or modified in an efficient matter. There is also the postSearchComplete
method that marks that the search operation is complete.
In order to effectively use the postSearchEntry
processing, Oracle Virtual Directory tends to use a special class known as an EntrySet
to handle result set processing. A get method returns results by returning a Vector, which includes one or more EntrySets (refer to "Creating EntrySets" for more information).
Each object in a directory is represented in Oracle Virtual Directory by a com.octetstring.vde.Entry
object. Each entry contains the name of the object and attributes with attribute values. All entry objects are processed in Oracle Virtual Directory using an implementation of the com.octetstring.vde.EntrySet
interface. Entry sets store or handle all entries returned by a particular data source. During normal Oracle Virtual Directory processing, each adapter called during the course of a search request will add its own EntrySet implementation to the list of results to be returned by Oracle Virtual Directory. Additionally, it is also possible that a plug-in could insert additional EntrySet objects into the results vector array. After all adapters have been queried to fulfill the search request, each EntrySet is traversed with its entries sent to the client.
While all adapters produce EntrySet implementations, a plug-in may also create an instance of the EntrySet interface and use it to return entries to the client during a search request.
The following are the two means a plug-in can use to create an EntrySet:
The simplest means a plug-in can use to create an EntrySet is by using the com.octetstring.vde.backend.extensible.ExtensibleEntrySet
class to create an EntrySet based on a java.util.Vector
of Entry
objects. The following is the procedure to do so:
Create a new java.util.Vector array.
Add all of the Entry objects to the vector.
Create a new instance of ExtensibleEntrySet passing the above Vector in the constructor.
Example 18-5 shows an example of plug-in using a Web Service to retrieve a stock price based on a stock symbol. The plug-in is designed to implement the concept of a Custom Adapter, which is an adapter that has no functionality itself and is a place holder where adapter level plug-ins can be configured to implement its functions instead. In this stock service example, the plug-in would be configured against a custom adapter. The plug-in is then responsible for handling all events, which means that you would expect that the stock service plug-in would not call the chain.getNext()
method.
The get method of the plug-in adds a list of Entry objects (that is, stock prices entries) to a Vector and creates an ExtensibleEntrySet based on that Vector:
Example 18-5 Example EntrySet Creation Using ExtensibleEntrySet
public void get(Chain chain, Credentials creds, DirectoryString base, Int8 scope, Filter filter, Bool typesonly, Vector attributes, Vector result) throws DirectoryException, ChainException { // Since this method is a handler, chain.getNext is not called. if (scope.intValue() == SearchScope.BASEOBJECT && base.equals(this.suffix)) { // handle the logical root of the adapter Entry root = this.getSimpleEntry(this.suffix); Vector entries = new Vector(); entries.add(root); result.add(new ExtensibleEntrySet(entries)); return; } //This adapter only supports searches based on an equality match //or an or'ing of equality matches if (filter.getSelector() != Filter.EQUALITYMATCH_SELECTED && filter.getSelector() != Filter.OR_SELECTED) { throw new DirectoryException("Only equality match or an or'ing "+ "of equality matches are allowed"); } Vector entries = new Vector(); //If the filter is an OR filter, we can iterate over every quote if (filter.getSelector() == Filter.OR_SELECTED) { Iterator it = filter.getOr().iterator(); while (it.hasNext()) { Entry entry = getStockEntry((Filter) it.next()); if (entry != null) { entries.add(entry); } } } else { //single quote Entry entry = getStockEntry(filter); if (entry != null) { entries.add(entry); } } //We use the ExtensibleEntrySet as a simple holder for entry sets. result.add( new ExtensibleEntrySet(entries)); }
While the use of ExtensibleEntrySet is the simplest means to create an EntrySet, it is not the most efficient because it requires that all results be compiled before processing is returned to the client. In this case, a call is made to the service for each term in the filter. A better way to process this request would be to create an EntrySet in such a way as to retrieve new entries as they are requested from the stock service, as in the LDAP Adapter operation.
When the LDAP Adapter EntrySet is asked for the next Entry, the system retrieves the next entry from the remote server one at a time—the way LDAP protocol is intended to work. This approach is more efficient as it allows the client to begin retrieving entries before all entries have been processed. This approach also allows the client to stop retrieval of entries and abort the query.
An implementation of com.octetstring.vde.EntrySet
must created for a plug-in to create an EntrySet that returns entries as requested. Each EntrySet must implement the following methods:
boolean hasMore()
Returns true if there are more entries in this EntrySet. This method must be non-destructive.
Entry getNext()
Returns the next entry in the EntrySet or null if there are no more entries.
void cancelEntrySet()
This method is called when an EntrySet will not be run to completion, allowing a custom EntrySet implementation to release any system resources it was holding.
Example 18-6 is the same plug-in implementation as Example 18-5 that creates an adapter out of a stock ticker Web Service, however, in Example 18-6, the get method only creates a list of symbols which gets passed off to the custom EntrySet. The get method in Example 18-6 adds a list of Entry objects (that is, stock prices entries) to a Vector and creates an ExtensibleEntrySet based on that Vector:
Example 18-6 Example get Method That Passes to a Custom EntrySet
public void get(Chain chain, Credentials creds, DirectoryString base, Int8 scope, Filter filter, Bool typesonly, Vector attributes, Vector result) throws DirectoryException, ChainException { if (scope.intValue() == SearchScope.BASEOBJECT && base.equals(this.suffix)) { Entry root = this.getSimpleEntry(this.suffix); Vector entries = new Vector(); entries.add(root); result.add(new ExtensibleEntrySet(entries)); return; } //This adapter only supports searches based on an equality match //or an or'ing of equality matches if (filter.getSelector() != Filter.EQUALITYMATCH_SELECTED && filter.getSelector() != Filter.OR_SELECTED) { throw new DirectoryException("Only equality match or an or'ing"+ " of equality matches are allowed"); } String rdn="uid"; ArrayList symbols = new ArrayList(); //If the filter is an OR filter, we can iterate over every quote if (filter.getSelector() == Filter.OR_SELECTED) { Iterator it = filter.getOr().iterator(); while (it.hasNext()) { //Extract the symbol from the filter String symbol = new String(filter.getEqualityMatch(). getAssertionValue().toByteArray()); //The attribute being checked in the equality search //doesn't really matter, but we need an RDN for each entry rdn = new String(filter.getEqualityMatch(). getAttributeDesc().toByteArray()); symbols.add(symbol); } } else { //single quote //Extract the symbol from the filter String symbol = new String(filter.getEqualityMatch(). getAssertionValue().toByteArray()); //The attribute being checked in the equality search doesn't //really matter, but we need an RDN for each entry rdn = new String(filter.getEqualityMatch(). getAttributeDesc().toByteArray()); symbols.add(symbol); } //We use the ExtensibleEntrySet as a simple holder for entry sets. result.add( new StockEntrySet(symbols.iterator(),rdn,this.base)); }
In Example 18-6, a list of stock symbols is created by iterating over the or in the search filter. The compiled list is passed to the custom EntrySet implementation as shown in Example 18-7:
Example 18-7 Example of Data Passed to Custom EntrySet
public class StockEntrySet implements EntrySet { Iterator quotes; String rdn; String base; public StockEntrySet(Iterator quotes, String rdn,String base) { this.rdn = rdn; this.quotes = quotes; this.base = base; } public Entry getNext() throws DirectoryException { Entry entry = this.getStockEntry((String) quotes.next()); if (entry == null) { if (this.hasMore()) { return this.getNext(); } else { return null; } } else { return entry; } } public boolean hasMore() { return quotes.hasNext(); } /** * Returns an entry for a stock quote * @param filter * @return An entry for the stock quote, or null for none. * @throws DirectoryException */ public Entry getStockEntry(String symbol) throws DirectoryException { //Create a new entry with the symbol as the RDN Entry entry = new Entry(new DirectoryString(rdn + "=" + symbol + "," + this.base)); //This uses an Apache Axis generated client stub NetXmethodsServicesStockquoteStockQuoteService service = new NetXmethodsServicesStockquoteStockQuoteServiceLocator(); try { NetXmethodsServicesStockquoteStockQuotePortType quoteService = service. getNetXmethodsServicesStockquoteStockQuotePort(); double value = quoteService.getQuote(symbol); if (value == -1) { return null; } //Create the attribute for the entry Vector vals = new Vector(); vals.add(new DirectoryString(symbol)); entry.put(new DirectoryString(rdn),vals); vals = new Vector(); vals.add(new DirectoryString("top")); vals.add(new DirectoryString("stockForOrganization")); entry.put(new DirectoryString("objectClass"),vals); vals = new Vector(); vals.add(new DirectoryString(Double.toString(value))); entry.put(new DirectoryString("quote"),vals); return entry; } catch (ServiceException e) { throw new DirectoryException("Could not load web service : " + e.getMessage()); } catch (RemoteException e) { throw new DirectoryException("Could not load web service : " + e.getMessage()); } } public void cancelEntrySet() { // nothing to do } }
The EntrySet implementation in Example 18-7 uses a java.util.Iterator to track which symbol is currently being processed. The StockEntrySet
class does not call out to the Web Service to create entry results until an Entry is requested by Oracle Virtual Directory on behalf of the client.
Since the plug-in supports the searching of one or more stocks, it is possible that not all searches will return valid results. Consider that if getNext
returns a NULL
result to Oracle Virtual Directory before the list of stocks is exhausted, Oracle Virtual Directory prematurely will assume the results are exhausted. To handle this situation, an extra block of code is added to getNext
after the call to getStockEntry
. If getStockEntry
returns a NULL
and the iteration through the requested stocks has not finished, getNext
will call itself in order to process the next candidate. This recursion will continue until at least one valid result is returned or all queries are exhausted.
LDAP filter possessing can be complicated. In the context of a Oracle Virtual Directory plug-in, there are two instances when it may be useful to parse a filter: pre-process or post-process. Each method offers it's own advantages and disadvantages and is not always mutually exclusive.
Post-Process Filtering
In post-process filtering, the com.octetstring.vde.util.FilterUtils.evalFilter(Entry e, Filter f)
method is used to see if an entry being returned as a result matches a required filter. This is the simplest way to handle filters and is useful when you are dealing with a small predefined data set that can remain in memory as a collection of Entry objects. This method is not generally the best solution when a filter needs to be translated into another format, for example, into a SQL WHERE
clause or a special object model for an external API.
Pre-Process Filtering
Pre-processing filters are used to parse a filter and to apply it to a modified search or transform it to another format that the target of the search can understand. Think of pre-processing filters as converting an LDAP filter to an SQL WHERE
clause, which to do so, you must traverse the filter object. For example, consider the conversion of the following LDAP filter to an SQL WHERE
clause:
(&(|(user=jsmith)(user=lswanson)(user=ccarson))(dept=payroll))
The preceding LDAP filter states All records where the user is jsmith, lswanson or ccarson and whose department is payroll. Figure 18-1 shows a visual representation of this LDAP filter:
To translate the filter shown in Figure 18-1 into an SQL WHERE
clause, you use a recursive function that traverses the tree. The example filter is represented in Oracle Virtual Directory as a hierarchy of Filter objects, which contain collections of other Filter objects to create a traversable tree. The filter shown in Figure 18-1 will have the object model shown in Figure 18-2, where the name of the class used to represent the filter element is below the operation or operand:
To traverse the tree shown in Figure 18-2, a recursive method is used that queries the getSelector()
method of the filter to determine what type of filter it is. After the type for the filter is determined, its value needs to be extracted by using a getFilterType
method. For example, if the filter is an equality filter, such as user=jsmith
, the value of the filter object would come from currentFilter.getEqualityMatch()
. In this case the return value is an AttributeValueAssertion
, which stores the attribute name and value as an Oracle. Once retrieved, the values can be converted into String objects. Filter_and
and Filter_or
objects return java.util.Iterator
classes for iterating through the child filters that are being operated on.
LDAP filters do not limit you to two terms per relation. The OR
portion has three operands. Since SQL only allows two operands per operation, the tree in Figure 18-2 must be converted to a binary tree.
In Figure 18-3, the OR
operation is broken up into two separate OR
operations. The final WHERE
clause from the filter is ((user=jsmith) OR ((user=lswanson) OR (user=ccarson))) AND (dept=payroll)
. The LDAP prefix notation has been transformed into a SQL like infix notation with only two operands per operation. Example 18-8 shows the source code for the transformation:
Example 18-8 Example Source Code for Transforming an LDAP Prefix Notation to SQL Notation
import com.octetstring.vde.util.*; import com.octetstring.ldapv3.*; import java.util.*; public class ConvertFilter { public static void main(String[] args) throws Exception { String ldapFilter = "(&(|(user=jsmith)(user=lswanson)" + (user=ccarson))(dept=payroll))"; System.out.println("Ldap Filter : " + ldapFilter); System.out.println("SQL WHERE : " + filterToSQL(ParseFilter.parse(ldapFilter))); } /** *Converts an ldap filter to an SQL WERE clause *@param currentFilter The filter being converted */ public static String filterToSQL(Filter currentFilter) { String[] filterVal; String infix=""; switch (currentFilter.getSelector()) { case Filter.EQUALITYMATCH_SELECTED : // (attrib=val) filterVal = getString(currentFilter.getEqualityMatch()); return filterVal[0] + "=" + filterVal[1]; case Filter.PRESENT_SELECTED : // (attrib=*) return new String(currentFilter.getPresent().toByteArray()) + "=*"; case Filter.GREATEROREQUAL_SELECTED : // (attrib>=val) filterVal = getString(currentFilter.getGreaterOrEqual()); return filterVal[0] + ">=" + filterVal[1]; case Filter.LESSOREQUAL_SELECTED : // (attrib<=val) filterVal = getString(currentFilter.getLessOrEqual()); return filterVal[0] + "<=" + filterVal[1]; case Filter.SUBSTRINGS_SELECTED : // (attrib=val*ue) filterVal = getString(currentFilter.getLessOrEqual()); return filterVal[0] + " LIKE " + filterVal[1]; case Filter.AND_SELECTED : // &((attrib=val)(attrib2=val2)) Filter_and andFilter = currentFilter.getAnd(); infix = ""; for (Iterator andEnum = andFilter.iterator(); andEnum.hasNext();) { Filter aFilter = (Filter) andEnum.next(); infix += "(" + filterToSQL(aFilter) + ") AND "; } infix = infix.substring(0,infix.lastIndexOf("AND")) + " "; return infix; case Filter.OR_SELECTED : // &((attrib=val)(attrib2=val2)) Filter_or orFilter = currentFilter.getOr(); infix = ""; for (Iterator orEnum = orFilter.iterator();orEnum.hasNext();) { Filter aFilter = (Filter) orEnum.next(); infix += " ( " + filterToSQL(aFilter) + " ) OR "; } infix = infix.substring(0,infix.lastIndexOf("OR")) + " "; return infix; case Filter.NOT_SELECTED : // !(&((attrib=val)(attrib2=val2))) return " NOT (" + filterToSQL(currentFilter.getNot()) + ") "; case Filter.APPROXMATCH_SELECTED : // (attrib~=val) filterVal = getString(currentFilter.getApproxMatch()); return filterVal[0] + " LIKE " + filterVal[1]; case Filter.EXTENSIBLEMATCH_SELECTED : //not standard return ""; //not supported } //will never reach return ""; } /** *Converts an AttributeValueAssertion to a two element array with the *first being the attribute name and the second being the value */ public static String[] getString(AttributeValueAssertion ava) { String matchAttr = new String(ava.getAttributeDesc().toByteArray()); String matchVal = new String(ava.getAssertionValue().toByteArray(),"UTF8"); return new String[] {matchAttr,matchVal}; } }
The sections in this topic provide a high-level introduction to the Oracle Virtual Directory classes that are available. Refer to the Oracle Fusion Middleware Java API Reference for Oracle Virtual Directory Javadoc for complete information on Oracle Virtual Directory classes. This topic contains the following sections:
The Virtual Service Interface (VSI) provides methods to make LDAP-like calls into the Oracle Virtual Directory. VSI works the same way regardless of whether the context is a global plug-in or an adapter level plug-in.
If there are three plug-ins in a chain and the first plug-in in the chain calls into VSI, then the context of the call is Oracle Virtual Directory as it appears through the second and third plug-in. If the call comes from the second plug-in, then it would only go through the third plug-in. If the call originates from the third plug-in, the call will not go through any plug-ins, regardless if the plug-in is global or local. If the plug-in is global, then the call will continue out of the global chain, through the Oracle Virtual Directory routing system into adapter level chains (depending on whether one or more adapters are selected by the router), which guarantees that not only will calls into Oracle Virtual Directory be consistent, but it also protects against infinite loops of a plug-in calling itself. The VSI is retrieved by using the chain object which is passed into every plug-in method.
The Global Service Interface (GSI) provides methods to make LDAP-like calls into the Oracle Virtual Directory as though they were coming from a end client. Each call is processed through the access control system (if enabled) and offers the ability to let the router select appropriate adapters for an operation. The GSI is the same interface that the LDAP Listener and Web Gateway use to communicate.
GSI can be retrieved by using the VSI by calling chain.getVSI().getGSI()
. With this handle, the add
, bind
, delete
, get
, getByDN
, modify
, and rename
methods can be called.
Warning:
With the GSI, it is possible for a plug-in to be caught in an infinite loop if it calls to a context above the current plug-in. Doing this can cause a scenario where the plug-in code is called repeatedly causing unanticipated results. Unless you intend for this to happen, be careful of scenarios where plug-ins call up the stack where looping might occur. In general, unless you need to call a specific adapter, it is always safest to use VSI.Oracle Virtual Directory provides no loop detection mechanisms. If you find that Oracle Virtual Directory has crashed with a custom plug-in due to a stack overflow or memory exhaustion, this is the most likely cause.
The Adapter Service Interface (ASI) provides methods to make LDAP-like calls into the Oracle Virtual Directory at the router level or directly to a specific adapter. The Oracle Virtual Directory Join View Adapter and its Joiners use ASI to communicate with adapters that are being searched and joined. The ASI interface is useful when you want to obtain information from an internal adapter, such as when configured to provide look-up information for a plug-in class.
ASI is retrieved via the VSI by calling chain.getVSI().getASI()
. With this handle, the add
, bind
, delete
, get
, getByDN
, modify
, and rename
methods can be called. Each method has two variations: one that provides a parameter for an adapter name, and another without. Use the method with adapter names to select specific adapters or use the other, nameless method to let the router select the appropriate adapters for you based on routing logic and routing configuration.
Warning:
With the ASI, it is possible for a plug-in to be caught in an infinite loop if it calls to a context above the current plug-in. Doing this can cause a scenario where the plug-in code is called repeatedly causing unanticipated results. Unless you intend for this to happen, be careful of scenarios where plug-ins call up the stack where looping might occur. In general, unless you need to call a specific adapter, it is always safest to use VSI.Oracle Virtual Directory provides no loop detection mechanisms. If you find that Oracle Virtual Directory has crashed with a custom plug-in due to a stack overflow or memory exhaustion, this is the most likely cause.
VSI, GSI and ASI all share a common interface, with certain interfaces providing extra functionality. For more information, refer to the Oracle Fusion Middleware Java API Reference for Oracle Virtual Directory.
The following list describes the supported ASI methods:
add()
Performs an LDAP add operation. Two versions of this method allow either the Oracle Virtual Directory Router to select the target adapter, or a specific adapter can be selected.
bind()
Performs an LDAP bind operation, either letting the Oracle Virtual Directory Router choose the adapter or applying to a specific adapter.
delete()
Performs an LDAP delete operation either letting the Oracle Virtual Directory Router choose the adapter or applying to a specific adapter.
get()
Performs an LDAP get operation letting the Oracle Virtual Directory Router choose eligible adapters. The get method returns a java.util.Vector of EntrySet values. An EntrySet is included for each adapter that was queried.
getbyDN()
A convenience method that performs an LDAP base search using a specific DN. The caller may choose to specify a specific adapter or may let the Oracle Virtual Directory Router choose.
modify()
Performs an LDAP modify operation. The caller may specify a specific adapter or may elect to have the Router choose automatically.
rename()
Performs an LDAP rename operation. The caller may specify specific from and to adapters or may elect to have the Router choose automatically.
The Oracle Virtual Directory Join View Adapter uses Joiners to join entries from a specific adapter and to merge them with entries from a primary adapter. A Joiner is an abstract class that defines the basic operations and methods required to implement a new Joiner. Joiners are called by the Join View Adapter whenever operations need to be performed against a joined entry. Joiners define pre-action operations to allow manipulation of data prior to any LDAP operation. Joiners also define mapOperationTargetByEntry
methods that allow the it to select a target entry in the target joined adapter depending on the operation being called.
A Joiner is instantiated with a primary adapter and a target adapter. The Join View Adapter always works in the context of the primary adapter and calls Joiner methods when mapping and when manipulations must be performed on a target joined adapter.
The get operation of the Join View Adapter builds a JoinEntrySet
based solely on results from the primary adapter. As the Oracle Virtual Directory client subsequently polls for results from the Oracle Virtual Directory, the JoinEntrySet
class calls the joiner JoinByEntry
method to make a call to the joined adapter and merge the entry results. If more than one join relationship is configured, the entry set processing will loop through all of the joins until the entry is fully joined based on all defined relationships.
The Joiner constructor method is called when the Joiner is instantiated by the Join View Adapter. This does not happen until the first LDAP operation is processed by the Join View Adapter (a form of lazy construction). The constructor is passed the configuration parameters for the joiner from the configuration file along with the associated target adapter name.
The createJoinFilter
method is usually a local method called by the JoinByEntry
method to create a search filter for a subsequent call to the AdapterServiceInterface
.
Oracle Virtual Directory supports the following utility classes:
PluginUtils
This class is a basic toolbox of mapping functions including renameAttribute
, copyAttribute
, and others. These classes are normally used in mapping scripts but are available for use in Java Plug-ins.
FilterTools
This class provides methods for creating and manipulating LDAP search filters.
ParseFilter
The ParseFilter
class provides ability to convert a String to a Filter and back again.
DNUtility
This class provides DN manipulation routines such as explode and create dn allowing manipulation of individual DN name components.
LDAPResult
The LDAPResult
is a utility class that can be used to compare the results returned from any method that returns an Int8
value. These constants help you translate result codes into LDAP error codes.
VDELogger
The Logger class provides an interface into the Oracle Virtual Directory logging facility (Log4J
). Use this class to integrate your console or audit messages with those of Oracle Virtual Directory.
PasswordEncryptor
This class provides various methods to encrypt string values into various hashed formats including Crypt, SHA, SSHA.
Oracle Virtual Directory supports the following utility classes:
Attribute
Attribute
is a basic object used in conjunction with the Entry class. An attribute defines a type (as in the attribute name) and contains its values. Methods are also provided for cloning and equivalence testing.
Credentials
A basic object holding the credentials of a session. The IP address, binddn, and password if needed, are in this object. Normally, for most operations relating to the AdapterServiceInterface
, only binddn
is relevant.
Entry
This object is used to hold an LDAP entry and it is used to contain partial entries such as with an LDAP modify request. The FilterTool
utilities often work with these objects to test filters.
EntryChange
This object contains an LDAP modify item. When handling modification requests, usually a Vector of EntryChange objects are passed to the AdapterServiceInterface
. Each EntryChange contains a single modification to a single entry.
EntrySet
An EntrySet contains a set of query results from an adapter. When a method first receives an EntrySet, the entire result set may not be in memory. Unique Entry objects are returned from an EntrySet by calling its getNext
method. Each time getNext
is called, the relevant adapter or plug-in class code is called to retrieve the next Entry if there is one. To test the availability of another entry, use the hasMore()
method.
Tip:
Unless you intend to process an entire result set, you should avoid callinggetNext()
directly. It is always better to let the LDAP client do this. In the case of a Oracle Virtual Directory plug-in class, a special method, postSearchEntry()
, is provided giving the ability to modify each entry as it is returned to the client. Needlessly calling getNext()
can cause excessive memory use and performance loss as Oracle Virtual Directory will be required to load an entire result set at once, rather than process entries as they arrive from the adapters.Filter
The Filter
object is a representation of a standard LDAP filter. This object provides useful methods for setting, testing, and comparing LDAP filters. The Filter object may contain a hierarchy of other filter objects (for example, Filter_and
, Filter_or
).
LDAPURL
This class provides methods to parse a standard LDAPURL
or to create one.
Oracle Virtual Directory supports the following data types:
BinarySyntax
Any binary value such as a password or user certificate.
DirectoryString
A case-insensitive string.
IASString
A case-sensitive string.
IntegerSyntax
An integer value
DistinguishedName
A distinguished name value (comparisons will follow DN equality rules).
Oracle Virtual Directory supports the following exceptions:
DirectoryBindException
Exception thrown when a bind is unsuccessful.
DirectoryException
A general exception thrown during any directory error. Check getMessage()
for more information, or getLDAPErrorCode()
to determine the LDAP error code. This exception may be generated by an adapter or by another plug-in.
DirectorySchemaViolation
A schema violation occurs when an attempted add or modify of an entry that violates either remote schema or local schema.
InvalidDNException
An InvalidDNException
is thrown by objects and utilities whenever an invalid DN is passed as a parameter.