This chapter describes the Oracle Virtual Directory mapping system based on the Python scripting language and contains the following sections:
Oracle Virtual Directory includes a bidirectional mapping system based on the Python scripting language. A Mapper is a special Python script, file type .mpy, that processes inbound and outbound transactional data flow within Oracle Virtual Directory. A mapping script can adjust requests as they enter the system on the way to data sources, and transform responses on the return path to the client. When compiled and deployed to the server a mapping script is known as a Mapper.
Note: If you rename attributes using Mappers, 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. |
Mappers are configured to run within the Oracle Virtual Directory Plug-in system in what is known as a plug-in chain. As with Plug-ins, a Mapper may be run globally or at an adapter level. Figure 10-1 shows a typical scenario where one mapping may be run on multiple adapters, while another may run only on a specific adapter.
Each mapping has an inbound and outbound flow, which means that a mapping can translate things one way as a request is received and reverse that translation as results are returned to the requesting application. Such programmatic reversal is important, as it is not usually possible for the server to guess the intent.
Most customers who use mappings use default mappings shipped with Oracle Virtual Directory to meet an application requirement. For customization needs, the Java plug-in API is typically used because the Mapper API only handles a subset of Java plug-in functionality and more developers know Java than Python, thus reducing time needed to develop the code.
When you create mappings, you can use a predefined mapping template included in Oracle Virtual Directory to simplify the mapping configuration. Oracle Virtual Directory provides several standard "boiler-plate" template mapping scripts. The mapping script names preceded by python are legacy scripts and are seen raw by Oracle Virtual Directory. The mapping script names that are not preceded by python are configurable using Oracle Virtual Directory Manager. To edit the code in the mapping script using Oracle Virtual Directory Manager, open the script using the Resource View (View->Resource) in Oracle Virtual Directory Manager.
The mapping templates with names preceded by OAM or EUS are for use with Oracle Access Manager and Enterprise User Security respectively. The documentation on the integrations with those technologies detail their mappings templates and their usage.
The following is a list and description of the Oracle Virtual Directory mapping templates:
Maps Microsoft Active Directory user and group objects to the InetOrgPerson inetOrgPerson and groupOfUniquenames objects (respectively).
Creates a virtual common name attribute by combining values from two attributes, default sn and givenname. The Common_Name_to_Given_Name mapping is typically used with the Database Adapter, which may have only a first and last name, but no full name.
Returns only the specified attribute if the conditional value in another attribute is met. The ConditionalPublish mapping is useful to hide FERPA protected attributes in a higher education environment.
Allows a database to be used to manage LDAP groups, in particular, the case where the membership value is only the RDN value instead of the complete DN. The DBGroups mapping expands the RDN value into a complete valid virtual DN.
Maps inbound binary syntax passwords to IA5String passwords compatible with the database.
Oracle mappers make extensive use of the Python language with additional Oracle provided functions for LDAP data manipulation. Refer to the following websites for more information on Python:
Python Programming Language Official Website at http://www.python.org/
Introduction and tutorial on Python at http://docs.python.org/tut/tut.html
The information in this section provides two examples for mappers:
The first example uses mappings to construct a common name (cn) from a givenname and sn. This mapper could be used with a Database Adapter where the cn value does not exist.
The second example makes Microsoft Active Directory look like a directory structured with inetorg objectclasses.
This example explains how to create a common name (cn) from a givenname and a surname, which could occur when using a database adapter to provide an LDAP interface to a user data stored in a database. While LDAP directories generally store a cn, databases tend to store only a first name and last name. When performing a search, this could become very complicated when filtering on common name. For example, the filter (cn=Marc Boorshtein)
would need to read (&(givenName=Marc)(sn=Boorshtein))
.
To simplify the process, mapping provides tools for manipulating filters as follows:
From string import split def parceCN(val): return split(val,' ',2) def inbound(): #map the "cn" filters if operation == 'get': if haveAttribute('cn'): addAttribute('givenName') addAttribute('sn') cnFilters = findFilters('cn') for filter in cnFilters: target,op,val = filter.contents givenNameVal, snVal = parceCN(val) givenNameFilter = createFilter('givenName',op,givenNameVal) snFilter = createFilter('sn',op,snVal) filter.contents = createAndFilter([givenNameFilter,snFilter]) ' def outbound(): #outbound stuff addAttributeValue('cn',getAttributeValue('givenName') + ' ' + getAttributeValue('sn'))Œ
The requirements for this mapper are fairly simple. When data is retrieved from the adapter, we would like to form a cn by combining givenname with sn. However on the inbound side, we want to split cn into givenname and sn. If cn is present in the attribute request list, the list will be changed to include givenname and sn. Finally, if the inbound operation is a search operation, we need to check the search filter and convert cn appropriately.
The outbound function handles all transactions that are flowing from the adapter to the client. In this example, we want to form a cn from two other values and we use the addAttributeValue function to create a new cn value by combining givenname, a space, and the sn value. Notice how existing values are retrieved using getAttributeValue function. This function retrieves a specific attribute from the current entry being returned to the client.
In the inbound() function we want to convert any CN into separate givenname and sn attributes. For a search, we want to convert search filters for cn into combined filter for givenname and sn. We will create a new function, parceCN(), to perform this task.
On the first line, the split function is imported from the Python string module. A Python function called parseCN() is defined that will take a common name (cn) and split it into a first and last name based on detecting a space.
Note: In reality, this is more complex, for example, what about middle names, but for the purposes of this example, we will cover this simple case. |
Next, we define our inbound() function. The inbound function could deal with any LDAP operation, but in this case, we are interested in looking at search operations. The first line after inbound is therefore an if
block that tests the value of operation. The variable operation contains one of add, bind, delete, get, modify, or rename.
If operation = get, the mapper proceeds by determining if the search request had cn in the attribute request list(). Since cn can only be formed by combining givenname and sn, we need to add givenname and sn to the search list using the addAttribute function.
In order to handle filter requests for cn, the mapper fetches all filter elements whose target is the cn attribute(). For each filter, the mapper parses it, calculates the corresponding givenname and sn values by calling parseCN, and creates new givenName and sn filters. Finally, it replaces the filter term with cn with a combined filter including the givenName and sn(').
It is common for an application to require the use of an LDAP directory using inetorgperson
and groupofuniquenames
schema objects. However, many organizations use Microsoft Active Directory which supports only user and group objects. To bridge this interoperability gap, a mapping can make an Active Directory schema look like inetorg style schema using inetorgperson
or groupofuniquenames
.
The translation between these two systems has multiple requirements, including:
Bidirectional mapping of attributes names. For example uniquemember = member
, uid = samaccountname
, and so on.
Conversion of objectclass names. Not only do the basic objectclass names need to change, but we must also consider that Microsoft Active Directory does not use auxiliary objectclasses. For example, objectclass values of interorgperson
, organizationalperson
, or person must be collapsed to just user
.
Adding special attribute values. Microsoft requires the use of additional object type codes such as groupType
or userAccountControl
. Depending on the operation, special tags must be added to the request.
RDN conversion. Microsoft typically uses cn as the relative distinguished name of user accounts. Many applications expect the use of uid.
def inbound(): #first rename the attributes rename({'uniqueMember':'member','uid':'samaccountname','userpassword': 'unicodepwd','ntgrouptype':'grouptype'})Œ #map nessasary object class values if haveAttributeValue('objectclass','groupifuniquenames'): removeAttributeValue('objectclass','groupofuniquenames') addAttributeValue('objectclass','group') if haveAttributeValue('objectclass','organizationalPerson'): removeAttributeValue('objectclass','organizationalPerson') addAttributeValue('objectclass','user') if haveAttributeValue('objectclass','inetOrgPerson'): removeAttributeValue('objectclass','inetOrgPerson') addAttributeValue('objectclass','user') #when adding an entry, certain values need to be added if operation == 'add': if haveAttributeValue('objectClass','group'): addAttributeValue('groupType','-2147483646') if not haveAttribute('samaccountname'): copy('cn','samaccountname') if haveAttributeValue('objectClass','user'): addAttributeValue('userAccountControl','66048') #collapse aux classes removeAttributeValue('objectClass','person') removeAttributeValue('objectClass','organizationalPerson') #set the rdn setRDN('samaccountname','cn') def outbound(): #first rename the attributes rename({'member':'uniqueMember','samaccountname':'uid','unicodepwd': 'userpassword','grouptype':'ntgrouptype'}) #map nessasary object class values if haveAttributeValue('objectclass','group'): removeAttributeValue('objectclass','group') addAttributeValue('objectclass','groupofuniquenames') if haveAttributeValue('objectclass','user'): removeAttributeValue('objectclass','user') addAttributeValue('objectclass','organizationalPerson')
Looking at the mapping above, there are two functions defined: inbound() and outbound(). The first line of inbound renames all inetorg attributes to Active Directory attributes. The rename function is called for all operations. For example, if the operation is a search, then all requested attributes will be renamed as well as all attributes in the filter. If the operation is an add or modify, then all attributes effected are renamed.
The next block replaces inetOrg object classes with InetAD ones. Notice that we are able to use conditional statements to determine what actions should be performed.
The third block checks to see if the operation is an add, and if so, it adds the specific attribute information required by Active Directory.
Next, all auxiliary object classes are removed because Active Directory does not allow for an auxiliary object class to be directly specified during an add.
Finally, the RDN is changed from uid to cn. Notice that the code converts samaccountname to cn because uid was already renamed to samaccountname. This does more than just change the rdn from a uid to cn, but it will deal with locating the cn if it's not specified (for example, in a modify or a search).
The outbound() function executes after a request is returned from Active Directory. This function reverses the inbound function by first renaming all applicable attributes, mapping the object class names and changing the rdn of any results. With a small script, an inetOrg application may use an Active Directory.
Mappings provide a simple and flexible way to manipulate requests and responses. You can transform and mold data into whatever form your application requires using mappings.
Mappers are based on Python and can use any functions or subroutines available within the Python language. In addition to Python, the following library functions are provided:
Note: For methods specifying "Map xxxxx", this means you can specify a list of values in the form:{'uniqueMember':'member','uid':'samaccountname',[…] } This is essentially an array of one or more mapped values. Use this construct for those methods that support it when a particular method is to be used multiple times for different named pair relationships (for example, rename in the preceding example script). This syntax not only is good shorthand, but also yields improved performance. |
The following information describes the additional methods provided:
operations: add, modify, get, entry
The appendAttribute
function adds the values of the source attribute to the destination attribute. The source attribute remains in place. This function will effect a search filter.
appendAttribute('sn','givenName') add/entry:dn: cn=User objectClass: person cn: User givenName: User sn: name becomes: dn: cn=User objectClass: person cn: User givenName: User givenName: name sn: name modify:dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First becomes: dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First givenName: Last get:(&(givenName=first)(sn=last)) becomes: (&(|(sn=last)(givenName=last))(givenName=first))
operations: add, modify, get, entry
The copyAttribute
function copies attribute values from the source attribute to the destination attribute, overwriting the destination attribute if it already exists.
copyAttribute('sn','givenName') add/entry: dn: cn=User objectClass: person cn: User givenName: User sn: name becomes: dn: cn=User objectClass: person cn: User givenName: User givenName: name sn: name modify:dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First becomes: dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First givenName: Last get:(&(givenName=first)(sn=last)) becomes: (|(sn=last)(givenName=last))
operations: add, modify, get, entry
Renames the source attribute to the destination attribute. If the destination attribute already exists, it is overwritten. If the source attribute does not exist, but the destination attribute does, the destination attribute is removed.
renameAttribute('sn','givenName') add/entry:dn: cn=User objectClass: person cn: User givenName: User sn: name becomes: dn: cn=User objectClass: person cn: User givenName: name modify: dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First becomes: dn: cn=User changetype: modify add: givenName givenName: Last get:(&(givenName=first)(sn=last)) becomes: (givenName=last)
operations: add, modify, get, entry
Removes the named attribute, returning its values in a list. If the attribute is a part of an entry, the values are returned. If the value is part of a changelist, the EntryChange object is returned.
removeAttribute('sn') add/entry:dn: cn=User objectClass: person cn: User givenName: User sn: name becomes: dn: cn=User objectClass: person cn: User givenName: User modify:dn: cn=User changetype: modify add: sn sn: Last - add: givenName givenName: First becomes: dn: cn=User changetype: modify add: givenName givenName: First givenName: Last get:(&(givenName=first)(sn=last)) becomes: (givenName=last)
operations: add, modify, entry, get
revalueAttribute('sn','name','newname') add/entry:dn: cn=User objectClass: person cn: User givenName: User sn: name becomes: dn: cn=User objectClass: person cn: User givenName: User sn: newname modify:dn: cn=User changetype: modify add: sn sn: name - add: givenName givenName: First becomes: dn: cn=User changetype: modify add: sn sn: newname - add: givenName givenName: First givenName: Last get:(&(givenName=first)(sn=name)) becomes: (&(givenName=last)(sn=newname))
operations: add, modify, entry
Maps a syntax value to a new syntax, or an attribute to a new syntax. If the first argument is a Syntax object, the function will return an instance of Syntax as named by newSyntax. If the first argument is the name of an attribute, all instances of that attribute are mapped to the new syntax. Valid syntaxes are: DirectoryString,IA5String,BinarySyntax,BinarySyntax.
operations: add, modify, entry
splitValue(['givenName','sn','cn',1) add/entry:dn: uid=User objectClass: person cn: First Last uid: User becomes: dn: uid=User objectClass: person givenName: First sn: Last uid: User modify:dn: uid=User changeType: modify replace: cn cn: First1 Last1 becomes: dn: uid=User changeType: modify replace: givenName givenName: First1 - replace: sn sn: :Last1 get:(cn=First Last) becomes: (&(givenName=First)(sn=last))
operations: add, modify, entry
Adds a value to the names attribute, or creates it if it does not exist. In the case of modify, if the attribute does not exist, then an Add modification item is created.
addAttributeValue('myattrib','myval') addAttributeValue('noattrib','hasvalue') add/entry:dn: uid=User objectClass: person cn: First Last uid: User myattrib: noval becomes: dn: uid=User objectClass: person givenName: First sn: Last uid: User myattrib: noval myattrib: myval noattrib: hasValue modify: dn: uid=User changeType: modify delete: myattrib myattrib: someval becomes: dn: uid=User changeType: modify delete: myattrib myattrib: someval myattrib: myval - changetype: add add: noattrib noattrib: hasValue
operations: add, modify, entry, get
Returns 1 or 0 (true or false) if the named attribute exists. If fetchFromServer is 1, then the entry is fetched from the server.
operations: add, modify, entry, get
Returns 1 or 0 (true or false) if the named attribute and associated value exists. If fetchFromServer is 1, then the entry is fetched from the server for comparison.
operations: add, modify, get, entry
Removes an attribute value, returning true if the value was removed.
removeAttributeValue('myattribute','myvalue') add/entry:dn: cn=user objectClass: person cn: user myattribute: myvalue myattribute: myvalue2 becomes: dn: cn=user objectClass: person cn: user myattribute: myvalue2 modify:dn: cn=User changetype: modify replace: myattribute myattribute: myvalue - add: sn sn: last becomes: dn: cn=User changetype: modify add: sn sn: last get: (&(sn=last)(myattribute=myvalue)) becomes: (sn=last)
operations: add, modify, delete, bind, rename, entry
Changes the RDN of the current name (base for get) from the old RDN to a new RDN attribute.
setRDN('cn','uid') add/entry: dn: cn=user objectClass: inetOrgPerson uid: userid cn: user becomes: dn: uid=userid cn: user objectClass: inetOrgPerson modify: dn: cn=user changetype: modify add: sn sn: last becomes: dn: uid=userid changetype: modify add: sn sn: last bind/get/delete: dn: cn=user becomes: dn: uid=userid
operations: get
Adds an attribute to the return attribute list during a search.
operations: get
Returns a list of all filters that involve the specified attribute.
operations: get
Creates a new filter object, with the target being the attribute being tested, the operation being one of the possible comparators and value being the value to filter on.
operations: get
Creates an and filter from a list of filters
operations: get
Creates an or filter from a list of filters
operations: add, entry
Returns the first value of the named attribute
operations: any
Retrieves that values of the named attribute from the supplied entry.
operations: modify
Creates and returns a new EntryChange object.
operations: modify
Adds en entry change to the list of entry changes.
operations: any
Returns the named entry.
operations: add, modify, entry, get
Replaces the oldBase with the newBase for the values of the named attribute.
Data objects are variables that are made available from Oracle Virtual Directory to you in the Python environment. Use these variables to get handles to Oracle Virtual Directory data structures and to interpret various objects and status items.
The current operation. Possible values are: add, modify, delete, rename, get, entry, and bind.
Retrieve a handle to VSI.
Attributes requested to be returned. Operations: get
The current search base. Operations: get
Returns and sets the filter in the form of a tuple (target, operation, value).
Returns TRUE if filter is an AND filter.
Returns TRUE if filter is an OR filter.
Returns TRUE if filter is a NOT filter.
Set of changes for a modify operation. Operations: modify
The current credentials (DN) of the user. Operations: All
The entry to be added or returned from a search. Operations: get, add
The current search filter. Operations: get
The entry to be added, bound, modified or deleted. Available for all operations.
Retrieve a Value(s): val = request([String name])
Store a Value(s): request(['myname'])='myvalue'
Returns and sets the current request information object attribute specified. This object is used as a method for passing arbitrary information between different mappers or plug-ins that exist for the duration of a specific transaction. For example, during an inbound operation, you can store information that can be used for processing later during the outbound request.
Returns and sets result code if an error occurs. Operations: Add, Delete, Modify
The current search scope in the form of 0, 1, or 2 (0 is base, 1 is onelevel, 2 is subtree). Operations: get
Whether the server is returning only types and not values. Operations: get