Skip navigation links

Package com.goldencode.p2j.directory

Implements a set of classes  that manage a hierarchy of directory objects and provide retrieval and editing capabilities for the objects and their attributes.

See: Description

Package com.goldencode.p2j.directory Description

Implements a set of classes  that manage a hierarchy of directory objects and provide retrieval and editing capabilities for the objects and their attributes.

Author(s)
Nick Saxon
Sergey Yevtushenko
Greg Shah
Date
November 18, 2010
Access Control
CONFIDENTIAL

Contents

Introduction
Participants
Definitions
Requirements
Primitive Directory Data Types
Directory Object Classes
Accessing Directory Objects
Enumerating Objects
Searching Directory
Enumerating Attributes
Retrieving Attribute Values
Editing Directory Objects
Modifying Attribute Values
Modifying Objects
Batch Editing Feature
Safe Directory Reloading
Safe Directory Backup
Meta Objects
Front-End Implementation
Schema Definition File Format
Directory Schema Loading
Back-End Implementation
Remapper Interface
Instantiation and Initialization
Querying Object Classes
Working with Objects and Attributes
Approach to Mapping
Configuration XML
LDAP Back-End
Mapping Directory Operations
Mapping Directory Objects
Configuration
Bulk Loading Configuration
Importing/Exporting the Directory
Directory Resource Plugin
API Summary
NodeAttribute Class
Attribute Class
DirectoryService Class
Initialization
Enumerations
Getting Attributes
Setting Attributes
Deleting Attributes
Working with Objects
Working with Meta Objects
Batch Editing
Predefined Object Classes
Internal classes
Mandatory single valued
Optional single valued
Optional multiple valued
Security Accounts Classes
Security Audit Classes
ACL Classes
Miscellaneous Security Classes
Appendix A. LDAP Schema Draft
Appendix B. LdapMapGen Utility
Appendix C. DirectoryEdit Utility
Appendix D. Directory Utilities

Introduction

The Directory Service is a package that runs on P2J servers.  It exposes a set of APIs as public methods through its front-end.  The front-end is how the rest of P2J perceives the P2J directory.  A back-end is what implements the persistent storage using one of many possible approaches.  The back-end is hidden inside the Directory Service package and is of no interest to the rest of the P2J environment.

Participants

Clients of Directory Service are various P2J server components.  P2J applications are legitimate clients of the Directory Service as long as they communicate locally.  There is no direct access via the network to the Directory Service or the back-end storage server.  The directory service API is available via method call from local server application code.  These APIs will be exported for remote access by authenticated P2J nodes via the com.goldencode.P2J.net package.  Each client is identified by its security context by the means of the SecurityManager. Further text of document uses terms client and security context as synonyms.

Directory Service Front-End is the portion of the package responsible for the logical view of the P2J directory.  It includes the API implementation the clients use to talk to the Directory Service.

Directory Service Back-End is the portion of the package that maps the logical view of the directory into some kind of persistent storage and maintains the semantics of P2J directory objects.

Definitions

A directory is a hierarchy of typed objects.  There is only one instance of a directory.  Directory objects form a tree.  A node in the tree can be a parent of the same type or another type of node.  Siblings do not have to be of the same type.

Types of directory objects are predefined.  Object types have names.  Types define what information can be stored in an instance of the type, what it is made of, what is mandatory or optional and whether nodes of a type may have children or should always remain leaves.

A directory object is a named set of attributes, each with associated value(s).  All attributes have unique names which serve as the keys to values and are of predefined primitive data types.  Attribute values may be single or multiple.  There is also a type of object without any attributes.   For attributes which support multiple values, values consist a set, so each value is unique.  The order of the values in the set is undefined.

Every directory object is a node in the directory tree and as such, has an ID.  Every node can be identified by a string which describes how to find the object in the tree from its root.  The path is made of links separated by '/' character like in file system directories.  Every link names a node in the subtree of its parent.  Sibling objects all have unique names, although the same names can be reused at different levels of the tree.  The root object has the ID of "".   Here are few examples.
   /security
/security/type3/data-abc
/security/account_x
Link names are case-insensitive.  That means that object names must differ in more than just string case.

Link names can be up to 256 characters in length.  Link names may only consist of alpha-numeric characters, dashes, periods and underscores.

Requirements

The design of the Directory Service should meet the following requirements:

Primitive Directory Data Types

The following table lists primitive data types defined in the Directory Service.

Primitive Data Type
Java Data Type
String Representation
Type Name String
integer
ATTR_INTEGER
int decimal number
INTEGER
boolean
ATTR_BOOLEAN
boolean "true" and "false"
BOOLEAN
string
ATTR_STRING
String character string
STRING
double
ATTR_DOUBLE
double decimal number with a "floating" decimal point
DOUBLE
bytearray
ATTR_BYTEARRAY
byte[] hexadecimal string
BYTEARRAY
bitfield
ATTR_BITFIELD
BitField, a subclass of BitSet which forbids dynamic set growth
string of 0s and 1s, enclosed in single quotes followed by 'B' suffix
BITFIELD
bitselector
ATTR_BITSELECTOR
BitSelector, a subclass of BitField which allows only one bit set to 1 at a time
string of 0s and 1s, enclosed in single quotes followed by 'B' suffix BITSELECTOR
date
ATTR_DATE
DateValue, a class which can be converted to and from java.util.Date, with time part ignored  and set to 0 string formatted as yyyy-mm-dd
DATE
time
ATTR_TIME
TimeValue, a class which can be converted to and from java.util.Date, with date part ignored  and set to 0 string formatted as hh:mm:ss
TIME

Directory Object Classes

A directory object class is a named collection of records, one record per attribute, plus some properties defined for the class. An attribute record specifies:
Class properties are:
Class information of objects forms a meta description. A meta description is a directory object which has the following attributes:
Instances of this meta description all have the same number of values in all multiple type attributes which can be indexed.  For example, a meta description of a class is also a class and as such it has its own object with the following attribute values:
The definition of all known object classes recognized by the directory establish directory schema.

Directory object classes cannot be defined dynamically.  The reason for excluding this feature is that clients of the Directory Service work only with well known objects. For this reason, the clients must be recompiled to take advantage of such changes.  This eliminates the value of dynamic schema changes.

The implementation of the directory package will use an XML file as a source for all object class definitions.  See Predefined Object Classes for the list of all currently defined classes and Schema Definition File Format for detailed description of the XML schema file.

Accessing Directory Objects

It is anticipated that the P2J directory is mostly used for information lookups.  This section discusses how it can be done.

Enumerating Objects

Any object in the directory can be a parent for zero or more other objects.  Enumerating allows for getting an array of children for a given directory object.  It is a simple API:
   public String[] enumerateNodes(String nodeId);
This call may return null, which means the specified object is either a leaf node or does not exist, or an array of object IDs, which are relative to the parent object ID.  An absolute ID can be produced by concatenating the original nodeID with the '/' character and a relative ID.  Relative IDs are just link names.

Security policy may limit visibility of objects.  Enumeration always returns only those objects that are visible to the caller according to the current security context.

Searching Directory

Arbitrary directory search is not provided, due to a belief it is of no value in P2J environment where the directory tree structure is well defined.

Enumerating Attributes

For any given directory object, it is possible to enumerate all attributes by a single API:
   public NodeAttribute[] enumerateNodeAttributes(String nodeId);
This call may return null, which means the specified object does not exist, or an array of node attributes.  Node attributes are just copies of the attribute records as described in Directory Object Classes, one per existing attribute, completed with the number of values.

Security policy may limit visibility of attributes.  Enumeration always returns only those attributes that are visible to the caller according to the current security context.

Retrieving Attribute Values

There is a set of APIs to retrieve a value of an attribute, one per primitive data type of the attribute.  They all differ only in the returned data type.  Here are sample APIs to retrieve an integer value:
   public Integer getNodeInteger(String nodeId, String name, int index);
public Integer[] getNodeIntegers(String nodeId, String name);
public Integer getNodeInteger(String nodeId, String name);
As can be seen, the first API returns a single value by its index, whereas the second API returns the whole set as an array.  Both may return null, which means the specified object or attribute does not exist.  Third method is a convenience method for first one with index parameter set to 0.

There is also a single API that returns all attributes with their values in one call:
   public Attribute[] getNodeAttributes(String nodeId);
This method returns null if the referenced object does not exist or has no attributes. Otherwise it returns an array of Attribute objects, which provide the following methods:
   public NodeAttribute getDefinition();
public int getCount();
public Object getValue(int index);
With these methods one can query the attribute name and type, find out the number of values and query any particular value of the attribute.  The value is returned as a generic object but the application can cast it to its primitive data type.

Editing Directory Objects

There are few things to keep in mind when using the Directory Service for editing the directory:
The following sections introduce the methods available for doing elementary edits (which cannot be executed outside of a batch session).  The final section of this chapter discusses the batch edit feature.

Modifying Attribute Values

There is a set of APIs to set a value of an attribute, one per primitive data type of the attribute.  They all differ only in the parameter data type.  Here are sample APIs to add and set an integer attribute value:
   public boolean setNodeInteger(String nodeId, String name, int index, int value);
public boolean addNodeInteger(String nodeId, String name, int value);
public boolean setNodeIntegers(String nodeId, String name, int[] value);
The first API sets a single value by its index.  The third API is a variation that replaces all existing values with those specified in array.  The second API just adds a new value to the existing set.  All return true if operation succeeds.  The attribute name should be one of the defined ones for this directory object class.

There is also a single API that replaces all existing attributes with a new set of attributes with their values in one call:
   public boolean setNodeAttributes(String nodeId, Attribute[] data);
The array of attributes for this call can be either obtained by calling getNodeAttributes or constructed as in the sample code below illustrating object creation. The new set of attributes should meet the requirements of the object class, namely, all mandatory attributes must be present in the array or the call fails.

To delete a single value of an attribute or the whole attribute, there are type-independent APIs:
   public boolean deleteNodeAttributeValue(String nodeId, String name, int index);
public boolean deleteNodeAttribute(String nodeId, String name);
If an attribute only has one value, the deletion of that sole value deletes the whole attribute.  Both calls fail if they would cause a mandatory attribute to be deleted, which is not allowed.

Modifying Objects

To create an object,  application calls an API and provides:
The requirements is to provide all mandatory attributes with their values.  The call fails if it is not met.  This is the API:
   public boolean addNode(String nodeId, String class, Attribute[] data);
For instance, to create an object of a class "namelist" that has attributes:
under the name "/groups/group_A", the application could do the following:
   // Let's assume NodeAttribute na1 and na2 were obtained previously
// na1 describes the ATTR_TIME attribute named "from"
// na2 describes the ATTR_STRING attribute named "list"

 // create the 1st attribute
Attribute attr1 = new Attribute(na1, new Time());

// create the 2nd attribute
Attribute attr2 = new Attribute(na2, new String[] {"a", "b", "c"});

// create an array of attributes
Attribute[] data = new Attribute[2];

// put attributes into the array
data[0] = attr1;
data[1] = attr2;

// create a new directory object
boolean result = addNode("/groups/group_A", "namelist", data);
The code above creates a new object which can be referenced as "/groups/group_A", having an attribute of type time named "from" with a single value, and an attribute of type string named "list" with 3 values: "a", "b", and "c".

To delete an object, use this API:
   public boolean deleteNode(String nodeId);
This call fails if the referenced object has children objects.  Those have to be deleted first.

Batch Editing Feature

Batch editing feature implemented in the Directory Service protects the users of the P2J directory from seeing partial updates being made to the directory. This feature enforces some discipline when editing the directory.

First of all, the application should open an editing session by calling:
   public boolean openBatch(String nodeId);
This call specifies a branch of the directory that should be considered busy for the lifetime of the editing session. The Directory Service uses internal batch editing lock manager to lock the specified branch and refuses request if it tries to lock a branch which is already locked by other client.

The lock only blocks parallel attempts to edit the same subtree. All regular queries of attributes are possible. Once the lock has been obtained and an editing session opened, the application is allowed to call editing methods discussed in previous sections. These calls do not have any immediate effect on the back-end, though. The Directory Service simply batches them internally. Therefore, the client which opened particular editing session sees changes as if they are already committed while other clients see unchanged directory.

Eventually, the application is done with editing. It calls:
   public boolean closeBatch(boolean disposition);
The disposition can be either commit the session, or discard all changes. Discarding changes is as easy as simply destroying the batch. Committing the session is done as follows:
This method provides collision detection and crash protection.  At startup, the Directory Service scans the directory and discovers all existing lock objects created by the same server.  The only explanation of their existence is the system crash. The locked branch of the directory may need to be backed out. This is the logic:
The implementation locates the backup by retrieving string attribute of the lock object. Also, multiple simultaneous locks put on different tree branches, require that multiple backup sets coexist.

Safe Directory Reloading

This API is designed to allow dynamic directory reloading from the backing XML file and, therefore, is available only for configurations with the XML back-end. This API is useful for quick manual directory file edits without the need to recycle the running server.

This is how it works:
If for any reason the function has to be cancelled, cancelRefresh() API can be called to release the lock and the internal cache remains unchanged.

Safe Directory Backup

This API is designed to allow safe directory backups into another XML file and, therefore, is available only for configurations with the XML back-end. This API is useful for backing up the directory without the need to recycle the running server.

This is how it works:

Meta Objects

Meta objects are directory objects that describe the directory.  They are there to allow applications query various pieces of information about directory objects in a regular directory way: by getting values of object attributes.

The meta objects are for reading only.

Class definitions are NOT meta objects, although they are very closely related.

Meta objects are dynamically created at runtime based on the current schema.

All meta objects stem from the "/meta" container (see Container for the definition).  The children of the "/meta" object are:
This is the parent for all objects of class "metaclass" describing all known directory object classes (see Directory Object Classes).  Every predefined class has the associated object in this container.
There is a terminal container object for every attribute name for every object class.  Being a read only terminal container, it is not very useful in general.  The purpose of their existence is to be able to specify directory security at the attribute level (in a directory access control list or ACL).  For instance, there is a "lock" object class having a "backed-up" attribute.  The corresponding meta object would be referenced as  "/meta/class/lock/backed-up".
The following API returns the object ID of the metaclass object for the given directory object:
   public String getNodeClass(String nodeId);
For instance, the call to getNodeClass("/meta") returns "/meta/class/container".  The latter object ID can be further used to query defined attributes and their properties.

Front-End Implementation

Front-ends define the logical view of data and manage the back-ends. There are few different implementations of the front-end:
In a given P2J environment, there can be only one of the given implementation's of the front-end in use.  The DirectoryService class is the main implementation of the front-end for the production environment.  The DirectoryCopy class is a stand-alone utility that is used to perform backup and recovery functions.

Schema Definition File Format

The directory schema is stored in one or more XML files.  The structure of the XML file assumes that for each object class there exists exactly one record which may contain zero or more object class attribute definition records.  The structure of the XML file follows:
Notes:
  1. classisleaf  is a class property (see Directory Object Classes) which is represented as strings true or false (case is ignored).
  2. classismutable is reserved and must be present as string false.
  3. Class attribute name, mandatory, multiple and immutable are appropriate attribute properties (see see Directory Object Classes for details) which are represented as strings true or false (case is ignored).
  4. Class attribute type represented as string (see Primitive Directory Data Types table for list of recognized "Type Name Strings").

Directory Schema Loading

The Java class SchemaLoad is used to initialize the directory schema using one or more XML resources loaded at runtime.

The main schema (which defines the standard object classes that must always exist for P2J) will be loaded from SchemaLoad.SYS_SCHEMA_NAME which is "dir_schema.xml".  The J2SE ClassLoader.getSystemResource() will be used to search the classpath and load the first instance of this file.  The file should be found in the jar for P2J and it should not be modified since the P2J code has heavy dependencies upon those definitions.

Applications may optionally add their own extensions to the directory schema.  This is done for such purposes as supporting custom security plugins using data stored in the directory.  Application schema extensions are loaded from SchemaLoad.EXT_SCHEMA_NAME which is "dir_schema_ext.xml" using ClassLoader.getSystemResources() which will return the list of all found resources.  There can be more than one, if there are enough jar files in the classpath that each contain that file.  It is valid for this file to be missing, it is completely optional.

Since there is no pathing information in either resource name, the associated files must be present in the root directory of the jar files being searched OR in the topmost directory of a path in the CLASSPATH.

This search process occurs regardless of any other runtime configuration.  The core P2J schema will be read first and then any application extensions will be read.  This is done very early in the initialization of the runtime environment, before the directory itself is active.  The reason loading from resources is a reasonable approach is that the source code for P2J (and optionally for applications that have their own directory schema extensions) is already hard coded to the specific schema.  For that reason, the schema upon which the code is dependent should be included with the code and there should be no need to require configuration in order to find it.

The format for both files is the same (see above), but the contents should generally not overlap.  If the application extension specifies an object class name that is the same as one that is already defined, it will replace that object class. Please note that this is VERY DANGEROUS unless you know exactly what you are doing.

Back-End Implementation

The logical view of the directory does not define how the objects are stored.  In general, any storage can be used, but some storage types are more convenient then others, because they allow tree structures to be handled easier.  Reasonable implementation choices are:
Pros: easy to implement; very good read performance
Cons: the directory cannot be easily distributed to another system; update of the large directory may be time consuming
Pros: integration with existing directory; update performance depends only on amount of changes, not on size of the directory
Cons: due to a high variety of LDAP structures, requires a mapping layer; average read performance

Integration with an existing directory or the ability to integrate later, is a key feature of this design.

Remapper Interface

The front-end communicates with the back-end using the Remapper interface, which has to be implemented by a back-end class.  The Remapper interface defines methods that the front-end delegates to the back-end for execution.  There are important differences, however:
All meta objects related requests are processed in the front-end, but this processing may cause translated requests to the real objects to be generated and passed down to the back-end for execution.
The front-end performs set operations by calling appropriate methods in a loop.
This task is carried out by the front-end.

These conventions make the back-end methods focus primarily on their storage and retrieval functions.

Classes implementing back-ends should allow multiple back-ends (but no more than one of each type) to be instantiated at the same time. This enables creation of directory copy/conversion utilities. One of the implemented classes is assigned as the back-end of choice through the bootstrap configuration.
Instantiation and Initialization
The front-end instantiates a back-end using one of the two defined techniques, both based on configuration variables.

The first technique uses short identifiers which allow front-end to choose one of the predefined back-ends. Given short identifier is converted to so called camel case (first character converted to the upper case, rest - to lower case). Then to identifier added suffix Remapper and prefix com.goldencode.p2j.directory. Resulting name is used to instantiate class using the Java reflection API.

The second technique uses full class name specified in configuration variable which is used to create instance of the back-end class using Java Reflection.

Regardless of the used technique, the back-end class implements the Remapper interface and has a constructor which accepts a single parameter - the instance of BootstrapConfig.

The rest of the sections describe the methods of the Remapper interface.
Querying Object Classes
This group of methods is used at startup to get the descriptions of the P2J directory object classes.

Method Signature
Description
String[] getClassNames();
Returns an array with names of all defined classes.
boolean isClassLeaf(String classname);
Class is leaf if no other object can be created under objects of the class. Also known as terminal.
boolean isClassImmutable(String classname);
Class is immutable if the back-end does not allow any change to its state.
AttributeDefinition[] getClassDefinition(String classname);
Returns an array of objects describing class attributes.

The array of AttributeDefinitions returned from getClassDefinition method call is different from the array of NodeAttributes which is part of the API. The following table lists methods of the AttributeDefinition class.

Method Signature
Description
String getName();
Returns the name of attribute.
int getType();
Returns the primitive data type of the attribute encoded as an integer value ATTR_* (see Primitive Directory Data Types)
boolean isMandatory();
Returns true if attribute is mandatory.
boolean isMultiple();
Returns true if attribute can have multiple values.
boolean isImmutable();
Returns true if attribute is readonly.
int getSize(); Returns exact size for fixed size attributes and 0 for unlimited size strings. The returned value is either the number of bytes for binary attributes, or the number of characters.

All methods listed so far should be made available for the front-end unconditionally. They are designed to be static in the following sense. The back-end is allowed to query the related information from its backing storage once at startup and then keep it in a memory cache. These methods do not have to be used within bind() and unbind() brackets.

Back-ends are free to chose a convenient storage method for all this information, but they have to provide a conversion tool that takes a XML file and creates the appropriate internal representation.
Working with Objects and Attributes
Methods listed below require a session delimited with a pair of calls to bind() and unbind().


Method Signature
Description
boolean bind();
Delegated calls
boolean unbind();
String[] enumerateNodes(String nodeId);
AttributeDefinition[] enumerateNodeAttributes(String nodeId);
Triggered by the API call
boolean deleteNodeAttributeValue(String nodeId, String name, int index); Delegated calls
boolean deleteNodeAttribute(String nodeId, String name);
boolean addNode(String nodeId, String class, String[] names, Object[] values);
boolean deleteNode(String nodeId);
boolean moveNode(String sourceId, String destinationId);
public Integer getNodeInteger(String nodeId, String name, int index);
public Boolean getNodeBoolean(String nodeId, String name, int index);
public String getNodeString(String nodeId, String name, int index);
public Double getNodeDouble(String nodeId, String name, int index);
public byte[] getNodeByteArray(String nodeId, String name, int index);
public BitField getNodeBitField(String nodeId, String name, int index);
public BitSelector getNodeBitSelector(String nodeId, String name, int index);
public Date getNodeDate(String nodeId, String name, int index);
public Time getNodeTime(String nodeId, String name, int index);
boolean setNodeInteger(String nodeId, String name, int index, int value);
boolean setNodeBoolean(String nodeId, String name, int index, boolean value);
boolean setNodeString(String nodeId, String name, int index, String value);
boolean setNodeDouble(String nodeId, String name, int index, double value);
boolean setNodeByteArray(String nodeId, String name, int index, byte[] value);
boolean setNodeBitField(String nodeId, String name, int index, BitField value);
boolean setNodeBitSelector(String nodeId, String name, int index, BitSelector value);
boolean setNodeDate(String nodeId, String name, int index, Date value);
boolean setNodeTime(String nodeId, String name, int index, Time value);
boolean addNodeInteger(String nodeId, String name, int value);
boolean addNodeBoolean(String nodeId, String name, boolean value);
boolean addNodeString(String nodeId, String name, String value);
boolean addNodeDouble(String nodeId, String name, double value);
boolean addNodeByteArray(String nodeId, String name, byte[] value);
boolean addNodeBitField(String nodeId, String name, BitField value);
boolean addNodeBitSelector(String nodeId, String name, BitSelector value);
boolean addNodeDate(String nodeId, String name, Date value);
boolean addNodeTime(String nodeId, String name, Time value);
 

Approach to Mapping

Mapping is a procedure of determination of a set of the backing storage identifiers, allowing retrieval and modification of a given P2J object's attribute. Mapping of P2J objects and attributes is the function of:
This is the most obvious dependency.  The back-end implementation would try to find the closest match from its pool of resources to represent objects of specific class and they most likely would be different.
Even similar P2J attributes may belong to different object classes in the backing storage.
This is the least obvious dependency. Instances of some class under one tree branch in P2J directory may require a different representation in the backing storage from the instances of the same class under another tree branch. There will be three distinctive cases:
  • P2J path: *
Object names or paths do not matter.
  • P2J path: specific name (like /x/y/z)
Mapping is specific to this instance.
  • P2J path: specific path (like /x/y/ with the terminating slash)
All instances of this class under this node.

This structure of the mapping information makes it a convenient attachment to the class definitions. Every class/attribute record gets associated with one (path:*) or more (other cases) of mapping records.

Although it may not be obvious, the structure of the mapping information also specifies the allowed paths where objects of a given class are allowed to be placed. Path:* acts as no limitation. Otherwise, the instances of the class the entry describes are allowed under specified path. Thus, multiple mappings may exist referring to the same back-end information, but specifying different paths where objects of the class can be placed.

Configuration XML

The configuration is kept in a XML file. This file is structured in a way that allows a separate utility class to provide the parsing of the file and calling the back-end's configure method to process the configuration information. Configuration information that is specific to multiple back-ends can be put there.

The XML file has the following elements and attributes (please note that back-ends may add extra attributes to any element):
Notes
  1. backendname specifies the back-end. Multiple back-ends may put their elements using their distinctive names.
  2. type and class specify two possible approaches to define back-end names (see Instantiation and Initialization for details).
  3. configuration element is a convenient way of putting some generic back-end configuration information using XML attributes.
  4. Mapping information is put under class, attribute, path elements using back-end specific XML attributes.

LDAP Back-End

LDAP is defined in RFC3377.  LDAP is also about a hierarchy of objects of various classes, standard and customized.   In theory, that makes a LDAP back-end implementation relatively straightforward.  However, there are additional considerations, that complicate the task:
A reasonable LDAP back-end implementation should address all listed issues.

Mapping Directory Operations

LDAP clients use sessions to query or modify directory entries.

P2J Directory Operation
Description
LDAP Operation
bind()
Authenticates the client to the server and creates a session
bind
unbind()
Terminates the session
unbind

The Directory Service maintains no more than one open session per security context through SecurityManager token management methods.  All clients of the Directory Service are authenticated to LDAP as the Directory Service.

P2J Directory Operation
Description
LDAP Operation
enumerate...()
get...()
Makes an LDAP server search a portion of the directory and return the requested results
search
n/a
Checks whether a specific attribute value exists
compare

P2J Directory Operation
Description
LDAP Operation
addNode()
Creates new objects in the directory.
add
deleteNode()
Deletes existing objects from the directory.
delete
addNode...()
setNode...()
deleteNodeAttribute...()
Changes the attributes and values contained within an existing entry.  New attributes can be added and existing attributes can be deleted.
modify
modify DN

Mapping Directory Objects
Following mapping approaches are possible:

First approach assumes that some kind of correspondence between LDAP schema and P2J schema is established and mapping information of the directory objects can be of two distinct types:
  1. Mapping between object classes or schema-level mapping;
  2. Mapping between nodes or node-level mapping;
The schema-level mapping defines how P2J and LDAP object classes map into each other, i.e. establish correspondence between P2J and LDAP object classes and their attributes. This is static information and can't be changed without restarting directory service.

The node-level mapping defines which P2J node corresponds to particular LDAP node. This is dynamic information which may change at run time as directory is updated.

This approach is rather simple, its performance is limited only by LDAP server and it allows implementation of the general purpose LDAP back-end which will support full set of directory operations: adding/removing/moving nodes, adding/removing/updating attributes. But this approach is not flexible enough to cover all possible mapping cases, because it does not allow to map one P2J object class into several P2J classes.

Second approach assumes that mapping should be done for each individual attribute of the each particular node. This approach does not have limitations on correspondence between object classes ans is more flexible but does not allow implementation of all directory operations because in general case there is no source of the information how to map new node into existing LDAP nodes and attributes or where to create new LDAP nodes and how to fill attributes unused by the P2J. Another problem is high resource consumption and questionable performance: each operation in may request data from several LDAP nodes and mapping information for the large directory may have significant size.

Third approach is similar to second one except it assumes that custom back-end is written for each particular existing LDAP directory and information required for creation nodes is obtained in some way which is also specific to this particular directory. Also, this approach may allow significantly reduce amount of mapping information by establishing some kind of rules which would allow to calculate mapping for each node and/or attribute instead of maintaining full mapping . This does allow to create fully functional directory but high resource consumption and questionable performance is still present.

Current implementation uses first approach.
Schema-level mapping
For the schema-level mapping there are two distinctive cases: an existing LDAP directory that needs mapping so that the P2J directory can reuse existing data, and an LDAP directory designed specifically to support P2J.  In both cases P2J can rely on presence of the LDAP schema which can be considered P2J-friendly, i.e. can be mapped into P2J schema relatively straightforward. For case of existing LDAP directory P2J-friendly schema is created by deriving new LDAP classes and attributes from existing ones. For case of specifically designed LDAP directory, appropriate LDAP schema can be automatically generated using LdapMapGen utility.

When LDAP directory is specifically designed for P2J then it is possible to map all non-meta P2J directory objects into real LDAP directory objects.  All functionality of the logical directory tree is available.  Every object class of the P2J directory maps into a specifically defined custom object class.  Thus, every P2J attribute can have the exact match in the LDAP directory, which makes the task of mapping objects straight forward.  The root of the P2J directory maps into a specified path on the LDAP directory, and the rest is a one-for-one correspondence.

The process of mapping consists of the following steps:
See RFC3703 for an example of a similar mapping.

When using existing LDAP directory then new schema should be written using one provides in Appendix A as a starting point. Each object class and attribute from the sample schema should be reviewed and derived from existing object classes and attributes to get as much information mapped into P2J as possible. Finally schema-level mapping information should be put into appropriate part of the LdapMapGen utility configuration file. Later this information will be used to generate complete mapping data for use by LDAP back-end.
Node-level mapping
For the node-level mapping situation is more complicated. There are three possible situations:
  1. Directory structure designed specifically for P2J
  2. Directory which contains all required data but has different structure
  3. Directory which does not contain all required data
In first case mapping is straightforward and can be done automatically at startup of the LDAP back-end. Since there is one-to-one correspondence between P2J and LDAP nodes resulting directory is completely functional for reading and writing.

In second case mapping data generation can automated with LdapMapGen utility and then maintained by LDAP back-end. In this case some P2J nodes have appropriate counterparts in the LDAP directory, but some intermediate (i.e. non-leaf) nodes will be artificial and have no corresponding LDAP nodes. Modification of such an artificial nodes can't be mapped into LDAP directory and therefore is not allowed. Otherwise resulting P2J directory is completely functional for reading and writing.

In third case mapping data can be partially generated with LdapMapGen utility and then missing parts should be added manually. In resulting P2J directory can be changed only nodes which have corresponding nodes in the LDAP, remaining nodes are read-only.

Configuration

All configuration information that the LDAP back-end needs is made of two sorts of data:
The LDAP access (bootstrap) configuration is part of the P2J server bootstrap configuration.  It consists of the bare minimum of information that would allow the Directory Service startup code to bind to the LDAP server using JNDI, and query the directory entries with the rest of the configuration information. The bootstrap information should include details needed for initial authentication of the back-end by LDAP:
All configuration data that is not part of the bootstrap configuration, is part of the configuration which can be stored in the LDAP directory itself. This should be no problem even for an existing LDAP directory, as all it takes is apackage.htmlnother custom object class for storing P2J directory configuration.

The P2J directory object class definitions is the core of the persistent configuration.  LDAP mapping information extends the class definitions with details of mapping, possibly down to the attribute level. See Appendix B for an example of such mapping.

Bulk Loading Configuration

The original definition of all pieces of persistent configuration information is kept in a XML file.  To be useful, this information has to be translated into a set of LDAP entries and attribute values.  This is done using a stand-alone utility LdapMapGen. The existing persistent configuration in LDAP directory is replaced completely.  This also means, that the XML configuration file can be considered a backup file for persistent configuration data.

Importing/Exporting The Directory

DirectoryCopy is a stand-alone utility that instantiates two back-ends simultaneously. The source back-end has the full access to the directory information. The target back-end is an empty directory (besides the class description information).

The utility makes calls as defined by the Remapper interface and traverses the tree copying the objects with their attributes from the source to the target. At the end, the target directory contains a full copy of the source.

Although DirectoryCopy allows any back-end to be copied to any other back-end, the most obvious use for this tool allows using the XML back-end file format as a persistence mechanism for the production directory.  In this case, one can easily import an entire directory using the source back-end as XML and the target back-end as LDAP.  Likewise, using a source back-end of LDAP and target back-end of XML allows an easy export of the current production directory into the standard XML back-end format.

Directory Resource Plugin

All Directory Service APIs are subject to access rights checks.  There is an abstract resource type named "directory" and a class that is the resource plugin.

The resource plugin considers these actions as the basis for the security decisions:

Action
Meaning for Nodes
Meaning for Attributes
no access
access is denied without further checks
enumerate
this node is visible when enumerating children of its parent node
this attribute is visible when enumerating all attributes of this node
create
this node can be created
a value can be set for the previously non-existent attribute
delete
this node can be deleted
the last (or the only) value of this attribute can be deleted
add
a child node can be added to this node
another value can be added to the existing attribute
read
meta class of this node can be read
attribute value can be read
write
this node is not readonly (can change state)
attribute value can be modified

The "directory" resource comes with a simple structure of access rights, made of two fields.  Here comes the description.

The field 1 describes the access rights which are mapped bitwise to the actions, plus a negation bit:

Field's primitive data type bitfield
Is it optional or mandatory? mandatory
Is it variable or fixed size? fixed
Field's size 7 bits
Field's displayable label "permissions"
Field's descriptive text Allowed actions
Bitfield's array of bit names "No access"
"Create"
"Delete"
"Add"
"Enumerate"
"Read"
"Write"
Bitfield's BitSet of unused bits "0000000"; all bits are used

The field 2 describes a generic condition which is optional:

Field's primitive data type string
Is it optional or mandatory? optional
Is it variable or fixed size? variable
Field's size unlimited
Field's displayable label "condition"
Field's descriptive text an arbitrary logical expression that, if present and evaluates to "false", denies access like "No access" permission.
Bitfield's array of bit names n/a
Bitfield's BitSet of unused bits n/a

The directory resource is hierarchical.  Permissions explicitly given to an object, are propagated down the tree for all objects having no explicitly set permissions.

The role of meta objects in the access rights checks is special.  They allow selective access by object class, besides the usual hierarchical system.  The rights, assigned to a metaclass object, determine the additional checks bound to all objects of that class.  Similarly, the rights assigned to attribute containers under "/meta/class/..." objects, determine the additional checks bound to all instances of that attribute.

Together, the regular hierarchy of objects under the "/security" branch (and others), and the meta hierarchy of objects under the "/meta" branch make a very powerful and flexible access control system.

API Summary

NodeAttribute Class

This class represents attribute enumeration results. This class does not provide public constructors. Objects of this class can only be obtained through a method call. This limitation corresponds to the static nature of the P2J directory schema.

Method Signature
Description
public String getName();
Returns the name of attribute.
public int getType();
Returns the primitive data type of the attribute encoded as an integer value ATTR_* (see Primitive Directory Data Types)
public boolean isMandatory();
Returns true if attribute is mandatory.
public boolean isMultiple();
Returns true if attribute can have multiple values.
public int getCount();
Returns the number of values assigned to the attribute.

Attribute Class

This class represents an attribute with values. Objects of this class can be instantiated or obtained through a method call. They are used as data keepers and as the source of data when creating new nodes, allowing for an easy object cloning.  A constructor should be provided that takes an existing Attribute instance.

Method Signature
Description
public NodeAttribute getDefinition();
Returns the NodeAttribute object which describes this attribute.
public Object getValue(int index);
Returns the indexed value of the attribute as a generic object, that can be cast into its primitive data type, or null if requested value doesn't exist.
public boolean addValue(Object value);
Adds new value to the set.  The attribute should either allow multiple values or currently have no value.  Returns true if successful.
public boolean setValue(int index, Object value);
Replaces the indexed value.  Returns true if successful.
public boolean deleteValue(int index);
Deletes the indexed value. If it is the only value, the attribute should be optional.  Returns true if successful.
public int getCount();
Returns the number of values currently assigned to the attribute.

DirectoryService Class

This class is a singleton.  It provides the Directory Service APIs.  They are listed below by category.
Initialization
Method Signature
Description
public static DirectoryService createInstance(BootstrapConfig); Creates and returns an instance of DirectoryService class.
public static DirectoryService getInstance(); Returns an existing instance of DirectoryService class.
public boolean bind(); Opens a session with the Directory Service.  Only one session can be open per client at a time.
public String getServerId(); Returns the server identification string from the bootstrap configuration.
public boolean unbind(); Closes the currently open session with the Directory Service.

Enumerations
Method Signature
Description
public String[] enumerateNodes(String nodeId); Returns an array of object IDs for all children of nodeId object.
public NodeAttribute[] enumerateNodeAttributes(String nodeId);
Returns an array of attribute definitions for all existing attributes of nodeId object.

Getting Attributes
Method Signature Description
public Integer getNodeInteger(String nodeId, String name, int index);
public Integer getNodeInteger(String nodeId, String name);
These methods take the object ID, attribute name and attribute value index and return an object that represents the selected value of the attribute, or null if query fails.  Single valued attributes should be queried as index 0. 
public Boolean getNodeBoolean(String nodeId, String name, int index);
public Boolean getNodeBoolean(String nodeId, String name);
public String getNodeString(String nodeId, String name, int index);
public String getNodeString(String nodeId, String name);
public Double getNodeDouble(String nodeId, String name, int index);
public Double getNodeDouble(String nodeId, String name);
public byte[] getNodeByteArray(String nodeId, String name, int index);
public byte[] getNodeByteArray(String nodeId, String name);
public BitField getNodeBitField(String nodeId, String name, int index);
public BitField getNodeBitField(String nodeId, String name);
public BitSelector getNodeBitSelector(String nodeId, String name, int index);
public BitSelector getNodeBitSelector(String nodeId, String name);
public Date getNodeDate(String nodeId, String name, int index);
public Date getNodeDate(String nodeId, String name);
public Time getNodeTime(String nodeId, String name, int index);
public Time getNodeTime(String nodeId, String name);
public Integer[] getNodeIntegers(String nodeId, String name);
These methods take the object ID and attribute name ad return an array of objects representing all existing values of the attribute, or null if query fails.
public Boolean[] getNodeBooleans(String nodeId, String name);
public String[] getNodeStrings(String nodeId, String name);
public Double[] getNodeDoubles(String nodeId, String name);
public byte[][] getNodeByteArrays(String nodeId, String name);
public BitField[] getNodeBitFields(String nodeId, String name);
public BitSelector[] getNodeBitSelectors(String nodeId, String name);
public Date[] getNodeDates(String nodeId, String name);
public Time[] getNodeTimes(String nodeId, String name);
public Attribute[] getNodeAttributes(String nodeId);
Returns the entire set of attributes with all their values, or null if query fails.
Setting Attributes
Method Signature Description
public boolean setNodeInteger(String nodeId, String name, int index, int value);
These methods take an object ID, attribute name, attribute value index and a value and change the indexed value of the attribute.
public boolean setNodeBoolean(String nodeId, String name, int index, boolean value);
public boolean setNodeString(String nodeId, String name, int index, String value);
public boolean setNodeDouble(String nodeId, String name, int index, double value);
public boolean setNodeByteArray(String nodeId, String name, int index, byte[] value);
public boolean setNodeBitField(String nodeId, String name, int index, BitField value);
public boolean setNodeBitSelector(String nodeId, String name, int index, BitSelector value);
public boolean setNodeDate(String nodeId, String name, int index, Date value);
public boolean setNodeTime(String nodeId, String name, int index, Time value);
public boolean addNodeInteger(String nodeId, String name, int value);
These methods take an object ID, attribute name, and a new value and the value to the existing set.
public boolean addNodeBoolean(String nodeId, String name, boolean value);
public boolean addNodeString(String nodeId, String name, String value);
public boolean addNodeDouble(String nodeId, String name, double value);
public boolean addNodeByteArray(String nodeId, String name, byte[] value);
public boolean addNodeBitField(String nodeId, String name, BitField value);
public boolean addNodeBitSelector(String nodeId, String name, BitSelector value);
public boolean addNodeDate(String nodeId, String name, Date value);
public boolean addNodeTime(String nodeId, String name, Time value);
public boolean setNodeIntegers(String nodeId, String name, int[] value);
These methods take an object ID, attribute name, and an array of new values and replace the existing values with the new ones.
public boolean setNodeBooleans(String nodeId, String name, boolean[] value);
public boolean setNodeStrings(String nodeId, String name, String[] value);
public boolean setNodeDoubles(String nodeId, String name, double[] value);
public boolean setNodeByteArrays(String nodeId, String name, byte[][] value);
public boolean setNodeBitFields(String nodeId, String name, BitField[] value);
public boolean setNodeBitSelectors(String nodeId, String name, BitSelector[] value);
public boolean setNodeDates(String nodeId, String name, Date[] value);
public boolean setNodeTimes(String nodeId, String name, Time[] value);
public setNodeAttributes(String nodeId, Attribute[] data);
Replaces all attributes with a new set at once.   Operation fails if the new set of attributes does not include all mandatory ones.
 
Deleting Attributes
Method Signature
Description
public boolean deleteNodeAttributeValue(String nodeId, String name, int index);
Takes an object ID, attribute name, and value index and deletes the indexed attribute value.
public boolean deleteNodeAttribute(String nodeId, String name);
Takes an object ID and attribute name and deletes the whole attribute.

Working with Objects
Method Signature
Description
public boolean addNode(String nodeId, String class, Attribute[] data);
Takes an object ID of a parent object, object class name, and arrays of attribute names and values and creates a new object.   Operation fails if the set of attributes does not include all mandatory ones.
public boolean deleteNode(String nodeId);
Takes an object ID and deletes the specified object.
public boolean moveNode(String nodeId, String newId);
Moves or renames the existing object, specified by nodeId. The parent object of the newId should exist and allow for children creation.

Working with Meta Objects
Method Signature
Description
public String getNodeClass(String nodeId);
Takes an object ID and returns the object ID of the metaclass object.

Batch Editing
Method Signature
Description
public boolean openBatch(String nodeId);
Returns true if an editing batch has been opened.  Fails if another batch is open for the calling security context or if the specified branch is already busy.
public boolean isEditing(); Returns true if an editing batch is currently open.
public boolean closeBatch(boolean disposition); Closes the currently open editing batch.  The disposition parameter tells what to do with the closed batch:
  • true means apply changes to the directory;
  • false means discard the batch.
Returns true if the requested disposition has been successfully applied.


Predefined Object Classes

Internal classes
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"container"

The simplest directory object class. The only function of containers is to be a parent to its children objects.  Class defines no attributes.
"terminal"
+
The simplest directory object class. The only function of terminals is to represent a valid  object ID.  Class defines no attributes.
"metaclass"

This class describes directory object classes
attrname
string
+
+
attribute names
attrtype
integer
+
+
encoded attribute types
attropt
boolean
+
+
mandatory/optional
attrmult
boolean
+
+
multiple/single valued

Mandatory single valued
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"integer"

Named integer value class
value
integer
+

value
"boolean"

Named boolean value class
value
boolean
+

value
"string"

Named string value class
value
string
+

value
"bytes"

Named bytearray value class
value
bytearray
+

value

Optional single valued
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"integerOption"

Named integer value class
option
integer


value
"booleanOption"

Named boolean value class
option boolean


value
"stringOption"

Named string value class
option string


value
"bytesOption"

Named bytearray value class
option bytearray


value

Optional multiple valued
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"integers"

Named integer value class
values
integer

+
values
"booleans"

Named boolean value class
values
boolean

+
values
"strings"

Named string value class
values string

+
valuess
"bytess"

Named bytearray value class
values bytearray

+
values

Security Accounts Classes
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"user"
+
Defines a user account. Object name is the user ID.
person
string


user name
alias
string
 
associates this account with X.509 certificate
password
bytearray


hashed password
pwsetdate date


date password was set; may be used for aging
pwsettime time


time password was set; may be used for aging
groups string

+
groups this user is assigned to
mode
integer


authentication mode override
"group"
+
Defines a group account. Object name is the group ID.
description
string


group description
"process"
+
Defines a process account. Object name is the process ID.
description
string


description
server boolean


defines process role as server (true) or application (false or omitted)
master
boolean


if set to true for a server, the server is a master server
alias
string


associates this account with X.509 certificate and, optionally,  PKCS#8 private key
secret bytearray


secret key servers use to access keystore

Security Audit Classes
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"auditDecision"
+
Defines audit modes for security decisions
success
boolean


if set to true, audits granted accesses
failure
boolean


if set to true, audits denied accesses
"auditResource"
+
Defines auditing per resource instance
instances string

+
optional resource instance names
requests integer

+
optional requested rights

ACL Classes
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"binding"
+
Defines an association between multiple subjects and resource instances.  Object names are sequential.
subjects
string
+
+
list of subject names (object names for user, group and process)
reftype
boolean
+
+
one flag per reference;

true - the reference refers to a specific resource instance
false - the reference refers to a match string
reference
string
+
+
list of references to resource instance names or match strings (regular expressions) that specify a match to multiple resource instance names
"systemRights"

Defines access rights object for the "system" resource type.  Object names are sequential.
check
string
+

user-defined logical expression that must evaluate true for access to be granted
"directoryRights"

Defines access rights object for the "directory" resource type.  Object names are sequential.
permissions
bitfield
+

7-bit bitfield, NCDAERW
condition
string


optional logical expression that must evaluate true for the access defined in the attribute named "permissions" to be granted
"netRights"

Defines access rights object for the "net" resource type.  Object names are sequential.
permissions bitfield +

4-bit bitfield, NRWX
"adminRights"

Defines access rights object for the "net" resource type.  Object names are sequential.
type
integer
+

type defines the meaning of the permissions
permissions
bitfield
+

type ADMT_PATH: 2-bit bitfield NU
type ADMT_USER: 8-bit bitfield NEPGRWCD

Miscellaneous Security Classes
Class Name
Terminal
Attribute Name
Primitive Type
Mandatory
Multivalued
Comments
"authMode"
+
Defines authorization mode details
anonymous string


name for anonymous account; if omitted, no anonymous connection allowed
mode
integer
+

authorization mode
plugin
string


optional authorization hook name
option
string


optional authorization hook parameters
retries
integer


optional authrntication retries mode
"lock"
+
Defines a directory lock object. Object name is the process ID of the server who created this object.
backed-up boolean


if present and set to true, backup is available
backupId
string


the location of the backup created before applying changes


Appendix A. LDAP Schema Draft

The following schema definition is experimental and can be considered a starting point in the implementation of LDAP back-end. The object identifiers used are those taken from the special dead space, which is good for experiments. A real implementation must be based on the IANA assigned object IDs.  As of this writing, the IANA application has not yet been processed to obtain a Golden Code unique object ID. 

Another note is about the back-end specific objects that keep the class definition information. They are not part of this schema, because it is up to the implementors of the LDAP back-end to chose what is the most convenient for them. As that piece is encapsulated in the back-end, no other part of the design is affected.

Having said that, this is a working implementation that can be used with OpenLDAP.

###########################################################################
# #
# GCD P2J Directory Schema #
# #
###########################################################################

# Macros ------------------------------------------------------------------

objectidentifier GCD 1.1
objectidentifier GCD.P2J GCD:2
objectidentifier GCD.P2J.OC GCD.P2J:1
objectidentifier GCD.P2J.At GCD.P2J:2

#---- attributes ----------------------------------------------------------

#---- basic attribute types -----------------

attributetype ( GCD.P2J.At:1 NAME 'gcdInteger'
DESC 'P2J directory integers attribute'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )

attributetype ( GCD.P2J.At:2 NAME 'gcdIntegers'
DESC 'P2J directory integers attribute'
EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27)

attributetype ( GCD.P2J.At:3 NAME 'gcdBoolean'
DESC 'P2J directory boolean attribute'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE )

attributetype ( GCD.P2J.At:4 NAME 'gcdBooleans'
DESC 'P2J directory booleans attribute'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7)

attributetype ( GCD.P2J.At:5 NAME 'gcdString'
DESC 'P2J directory string attribute'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )

attributetype ( GCD.P2J.At:6 NAME 'gcdStrings'
DESC 'P2J directory strings attribute'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)

attributetype ( GCD.P2J.At:7 NAME 'gcdByteArray'
DESC 'P2J directory bytearray attribute'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
SINGLE-VALUE )

attributetype ( GCD.P2J.At:8 NAME 'gcdByteArrays'
DESC 'P2J directory bytearrays attribute'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40)

attributetype ( GCD.P2J.At:9 NAME 'gcdBitField'
DESC 'P2J directory bitfield attribute'
EQUALITY bitStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.6
SINGLE-VALUE )

attributetype ( GCD.P2J.At:10 NAME 'gcdBitFields'
DESC 'P2J directory bitfields attribute'
EQUALITY bitStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.6)

attributetype ( GCD.P2J.At:11 NAME 'gcdBitSelector'
DESC 'P2J directory bitselector attribute'
EQUALITY bitStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.6
SINGLE-VALUE )

attributetype ( GCD.P2J.At:12 NAME 'gcdBitSelectors'
DESC 'P2J directory bitselectors attribute'
EQUALITY bitStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.6)

attributetype ( GCD.P2J.At:13 NAME 'gcdDate'
DESC 'P2J directory date attribute'
EQUALITY caseExactMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )

attributetype ( GCD.P2J.At:14 NAME 'gcdDates'
DESC 'P2J directory dates attribute'
EQUALITY caseExactMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)

attributetype ( GCD.P2J.At:15 NAME 'gcdTime'
DESC 'P2J directory time attribute'
EQUALITY caseExactMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )

attributetype ( GCD.P2J.At:16 NAME 'gcdTimes'
DESC 'P2J directory times attribute'
EQUALITY caseExactMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)

# No replacement for the Double.

#---- derived attribute types -----------------

attributetype ( GCD.P2J.At:100 NAME 'gcdAuditDecisionSuccess' SUP gcdBoolean)
attributetype ( GCD.P2J.At:101 NAME 'gcdAuditDecisionFailure' SUP gcdBoolean)
attributetype ( GCD.P2J.At:102 NAME 'gcdAuditResourceType' SUP gcdString)
attributetype ( GCD.P2J.At:103 NAME 'gcdAuditResourceInstances' SUP gcdStrings)
attributetype ( GCD.P2J.At:104 NAME 'gcdAuditResourceRequests' SUP gcdIntegers)
attributetype ( GCD.P2J.At:105 NAME 'gcdAuthModeAnonymous' SUP gcdString)
attributetype ( GCD.P2J.At:106 NAME 'gcdAuthModeMode' SUP gcdInteger)
attributetype ( GCD.P2J.At:107 NAME 'gcdAuthModePlugin' SUP gcdString)
attributetype ( GCD.P2J.At:108 NAME 'gcdAuthModeOption' SUP gcdString)
attributetype ( GCD.P2J.At:109 NAME 'gcdBindingReftype' SUP gcdBoolean)
attributetype ( GCD.P2J.At:110 NAME 'gcdBindingReference' SUP gcdString)
attributetype ( GCD.P2J.At:111 NAME 'gcdBooleanValue' SUP gcdBoolean)
attributetype ( GCD.P2J.At:112 NAME 'gcdBooleanOptionOption' SUP gcdBoolean)
attributetype ( GCD.P2J.At:113 NAME 'gcdBooleansValues' SUP gcdBooleans)
attributetype ( GCD.P2J.At:114 NAME 'gcdBytesValue' SUP gcdByteArray)
attributetype ( GCD.P2J.At:115 NAME 'gcdBytesOptionOption' SUP gcdByteArray)
attributetype ( GCD.P2J.At:116 NAME 'gcdBytessValues' SUP gcdByteArrays)
attributetype ( GCD.P2J.At:117 NAME 'gcdDateOptionOption' SUP gcdDate)
attributetype ( GCD.P2J.At:118 NAME 'gcdDatesValues' SUP gcdDates)
attributetype ( GCD.P2J.At:119 NAME 'gcdDirectoryRightsPermissions' SUP gcdBitField)
attributetype ( GCD.P2J.At:120 NAME 'gcdDirectoryRightsCondition' SUP gcdString)
attributetype ( GCD.P2J.At:121 NAME 'gcdGroupDescription' SUP gcdString)
attributetype ( GCD.P2J.At:122 NAME 'gcdIntegerValue' SUP gcdInteger)
attributetype ( GCD.P2J.At:123 NAME 'gcdIntegerOptionOption' SUP gcdInteger)
attributetype ( GCD.P2J.At:124 NAME 'gcdIntegersValues' SUP gcdIntegers)
attributetype ( GCD.P2J.At:125 NAME 'gcdLockBackedUp' SUP gcdBoolean)
attributetype ( GCD.P2J.At:126 NAME 'gcdLockBackupId' SUP gcdString)
attributetype ( GCD.P2J.At:127 NAME 'gcdNetRightsPermissions' SUP gcdBitField)
attributetype ( GCD.P2J.At:128 NAME 'gcdProcessDescription' SUP gcdString)
attributetype ( GCD.P2J.At:129 NAME 'gcdProcessServer' SUP gcdBoolean)
attributetype ( GCD.P2J.At:130 NAME 'gcdProcessMaster' SUP gcdBoolean)
attributetype ( GCD.P2J.At:131 NAME 'gcdProcessAlias' SUP gcdString)
attributetype ( GCD.P2J.At:132 NAME 'gcdProcessSecret' SUP gcdByteArray)
attributetype ( GCD.P2J.At:133 NAME 'gcdStringValue' SUP gcdString)
attributetype ( GCD.P2J.At:134 NAME 'gcdStringOptionOption' SUP gcdString)
attributetype ( GCD.P2J.At:135 NAME 'gcdStringsValues' SUP gcdStrings)
attributetype ( GCD.P2J.At:136 NAME 'gcdSystemRightsCheck' SUP gcdString)
attributetype ( GCD.P2J.At:137 NAME 'gcdUserPerson' SUP gcdString)
attributetype ( GCD.P2J.At:138 NAME 'gcdUserAlias' SUP gcdString)
attributetype ( GCD.P2J.At:139 NAME 'gcdUserPassword' SUP gcdByteArray)
attributetype ( GCD.P2J.At:140 NAME 'gcdUserPwsetdate' SUP gcdDate)
attributetype ( GCD.P2J.At:141 NAME 'gcdUserPwsettime' SUP gcdTime)
attributetype ( GCD.P2J.At:142 NAME 'gcdUserGroups' SUP gcdStrings)
attributetype ( GCD.P2J.At:143 NAME 'gcdUserMode' SUP gcdInteger)

#---- object classes --------------------------------------------------------


# auditDecision
objectclass ( GCD.P2J.OC:1 NAME 'gcdAuditDecision'
DESC 'P2J auditDecision class'
SUP top STRUCTURAL
MUST cn
MAY (gcdAuditDecisionSuccess $ gcdAuditDecisionFailure))


# auditResource
objectclass ( GCD.P2J.OC:2 NAME 'gcdAuditResource'
DESC 'P2J auditResource class'
SUP top STRUCTURAL
MUST (cn $ gcdAuditResourceType)
MAY (gcdAuditResourceInstances $ gcdAuditResourceRequests))


# authMode
objectclass ( GCD.P2J.OC:3 NAME 'gcdAuthMode'
DESC 'P2J authMode class'
SUP top STRUCTURAL
MUST (cn $ gcdAuthModeMode)
MAY (gcdAuthModeAnonymous $ gcdAuthModePlugin $ gcdAuthModeOption))


# binding
objectclass ( GCD.P2J.OC:4 NAME 'gcdBinding'
DESC 'P2J binding class'
SUP top STRUCTURAL
MUST (cn $ gcdBindingReftype $ gcdBindingReference))


# boolean
objectclass ( GCD.P2J.OC:5 NAME 'gcdBoolean'
DESC 'P2J boolean class'
SUP top STRUCTURAL
MUST (cn $ gcdBooleanValue))


# booleanOption
objectclass ( GCD.P2J.OC:6 NAME 'gcdBooleanOption'
DESC 'P2J booleanOption class'
SUP top STRUCTURAL
MUST cn
MAY gcdBooleanOptionOption)


# booleans
objectclass ( GCD.P2J.OC:7 NAME 'gcdBooleans'
DESC 'P2J booleans class'
SUP top STRUCTURAL
MUST cn
MAY gcdBooleansValues)


# bytes
objectclass ( GCD.P2J.OC:8 NAME 'gcdBytes'
DESC 'P2J bytes class'
SUP top STRUCTURAL
MUST (cn $ gcdBytesValue))


# bytesOption
objectclass ( GCD.P2J.OC:9 NAME 'gcdBytesOption'
DESC 'P2J bytesOption class'
SUP top STRUCTURAL
MUST cn
MAY gcdBytesOptionOption)


# bytess
objectclass ( GCD.P2J.OC:10 NAME 'gcdBytess'
DESC 'P2J bytess class'
SUP top STRUCTURAL
MUST cn
MAY gcdBytessValues)


# container
objectclass ( GCD.P2J.OC:11 NAME 'gcdContainer'
DESC 'P2J container class'
SUP top STRUCTURAL
MUST cn)


# dateOption
objectclass ( GCD.P2J.OC:12 NAME 'gcdDateOption'
DESC 'P2J dateOption class'
SUP top STRUCTURAL
MUST cn
MAY gcdDateOptionOption)


# dates
objectclass ( GCD.P2J.OC:13 NAME 'gcdDates'
DESC 'P2J dates class'
SUP top STRUCTURAL
MUST cn
MAY gcdDatesValues)


# directoryRights
objectclass ( GCD.P2J.OC:14 NAME 'gcdDirectoryRights'
DESC 'P2J directoryRights class'
SUP top STRUCTURAL
MUST (cn $ gcdDirectoryRightsPermissions)
MAY gcdDirectoryRightsCondition)


# group
objectclass ( GCD.P2J.OC:15 NAME 'gcdGroup'
DESC 'P2J group class'
SUP top STRUCTURAL
MUST cn
MAY gcdGroupDescription)


# integer
objectclass ( GCD.P2J.OC:16 NAME 'gcdInteger'
DESC 'P2J integer class'
SUP top STRUCTURAL
MUST (cn $ gcdIntegerValue))


# integerOption
objectclass ( GCD.P2J.OC:17 NAME 'gcdIntegerOption'
DESC 'P2J integerOption class'
SUP top STRUCTURAL
MUST cn
MAY gcdIntegerOptionOption)


# integers
objectclass ( GCD.P2J.OC:18 NAME 'gcdIntegers'
DESC 'P2J integers class'
SUP top STRUCTURAL
MUST cn
MAY gcdIntegersValues)


# lock
objectclass ( GCD.P2J.OC:19 NAME 'gcdLock'
DESC 'P2J lock class'
SUP top STRUCTURAL
MUST cn
MAY (gcdLockBackedUp $ gcdLockBackupId))


# netRights
objectclass ( GCD.P2J.OC:20 NAME 'gcdNetRights'
DESC 'P2J netRights class'
SUP top STRUCTURAL
MUST (cn $ gcdNetRightsPermissions))


# process
objectclass ( GCD.P2J.OC:21 NAME 'gcdProcess'
DESC 'P2J process class'
SUP top STRUCTURAL
MUST cn
MAY (gcdProcessDescription $ gcdProcessServer $ gcdProcessMaster $ gcdProcessAlias $ gcdProcessSecret))


# string
objectclass ( GCD.P2J.OC:22 NAME 'gcdString'
DESC 'P2J string class'
SUP top STRUCTURAL
MUST (cn $ gcdStringValue))


# stringOption
objectclass ( GCD.P2J.OC:23 NAME 'gcdStringOption'
DESC 'P2J stringOption class'
SUP top STRUCTURAL
MUST cn
MAY gcdStringOptionOption)


# strings
objectclass ( GCD.P2J.OC:24 NAME 'gcdStrings'
DESC 'P2J strings class'
SUP top STRUCTURAL
MUST cn
MAY gcdStringsValues)


# systemRights
objectclass ( GCD.P2J.OC:25 NAME 'gcdSystemRights'
DESC 'P2J systemRights class'
SUP top STRUCTURAL
MUST (cn $ gcdSystemRightsCheck))


# terminal
objectclass ( GCD.P2J.OC:26 NAME 'gcdTerminal'
DESC 'P2J terminal class'
SUP top STRUCTURAL
MUST cn)


# user
objectclass ( GCD.P2J.OC:27 NAME 'gcdUser'
DESC 'P2J user class'
SUP top STRUCTURAL
MUST uid
MAY (gcdUserPerson $ gcdUserAlias $ gcdUserPassword $ gcdUserPwsetdate $ gcdUserPwsettime $ gcdUserGroups $ gcdUserMode))

## end of schema

Appendix B. LdapMapGen Utility

General Description


The design of the LDAP back-end allows different modes of operation but all of them based on the mapping information collected in some way.  The mapping information (or just 'mapping') tells LDAP back-end how to represent LDAP server to P2J DirectoryService users.  The mapping contains two main parts: schema-level mapping and node-level mapping.  The schema-level mapping defines how particular LDAP object class and its attributes corresponds to P2J object classes and attributes.  The node-level mapping defines which LDAP node represents particular node in P2J directory. 

The LDAP back-end supports two major modes of operation: when only schema-level mapping is defined and when both schema-level and node-level mapping are provided.  The difference between them is that first one assumes that LDAP server already provides hierarchy suitable for use by P2J and LDAP back-end just uses it as is.  In second mode there is no need to have such a hierarchy, each P2J node can be individually mapped into LDAP node and some nodes may not have corresponding LDAP node at all.  Since writing mapping information manually is complicated and error prone procedure, LdapMapGen utility is provided to simplify it. 

The main purpose of the LdapMapGen utility is to generate mapping information required for use of P2J DirectoryService subsystem with LDAP server.  The resulting mapping information can be stored in the map file in XML format recognized by LDAP back-end or at the LDAP server in specified location.  Beside that, utility in some cases can generate schema file suitable for use with LDAP server.  This function has limitations explained below

At startup utility reads the configuration file and parses it.  Then using configuration data and command line parameters it produces output files or updates mapping information on the LDAP server.  The configuration file contains two different kinds of information: the configuration data (such as LDAP connection details, location of schema file, etc.) and mapping rules.  Mapping rules are split into two parts: schema-level mapping and node-level mapping.  The utility can in some cases generate proper mapping information without mapping data at all or with partial mapping data in the configuration file. 

Modes of Operation


The utility supports two modes of operation: direct (or subtree) and node-to-node.  First mode assumes that LDAP directory structure should be mapped as is into P2J directory.  Second mode requires node to node mapping data.  To simplify writing mapping data in configuration file for second mode LdapMapGen utility supports mapping of subtrees (see below description of configuration file format). 
Beside these common modes there is a special case of first mode, when no mapping information is provided at all.  In this case necessary mapping data generated automatically from the P2J schema definition file.  Also, for this situation LdapMapGen can generate LDAP schema file which exactly corresponds to the generated mapping. 

Configuration File Format

Configuration file is an XML file of following format:

<?xml version="1.0"?>
<!-- LdapMapGen configuration file -->

<configuration-root>
   <config .../>
   <class-mapping>
      <class .. >
         <attribute .../>
         ...
      </class>
      ...
   </class-mapping>

   <node-mapping>
      <node .../>
   </node-mapping>
</configuration-root>

Recognized attributes

Node config. This node holds configuration information.

Attribute
Description
mapping-mode
Mode of the operation: perform schema-level mapping only or both, schema-mapping and node-level mappings.
ulr
LDAP server URL. URL format is dictated by the JNDI interface and looks so:
protocol://host-name[:port]/ldap-root

Where:
protocol either ldap or ldaps. ldap is for ordinary connections and ldaps for the TLS connections.
host-name name of the host where LDAP server is running.
port  optional port name. Unless LDAP server is configured for non-standard ports, it uses port 389 for regular connections and port 636 for TLS connections.
ldap-root distinguished name in the LDAP directory for initial bind.
principal
The identity information used to authenticate to the LDAP server.
credentials
The password.
mapping-destination
The LDAP node where resulting mapping can be stored. If node does not exists it will be created.
mapping-attribute
The attribute name where mapping will be stored.
mapping-object-class
Object class name of the LDAP node where mapping will be stored.
ldap-schema-header
The content of this attribute is used during generation of LDAP schema file. The  content is written at the beginning of the generated files as is, without changes. The main purpose is to provide definition of LDAP attributes used by rest of the generated schema.

Note:
  1. The mapping destination node, node object class and node attribute name parameters are necessary only if saving mapping in the LDAP is required. If specified node does not exists, it will be created. The desired object class for the node should not have other mandatory attributes except common name. The destination attribute where mapping will be stored should support of storing of multiple values of directory string or compatible type.
  2. Schema header attribute is optional.
Node class. This node holds description for single P2J to LDAP class mapping. Configuration file should contain separate class node for each class to class mapping.

Attribute
Description
ldap
LDAP object class name.
p2j
P2J object class name.

Node attribute. This node holds description for single P2J to LDAP attribute mapping. Configuration file should contain separate attribute node for each LDAP attribute to P2J attribute mapping.

Attribute
Description
ldap
LDAP object class attribute name.
p2j
P2J object class attribute name.

Note: For each class at least one special attribute mapping must be defined: special P2J attribute objectName should be mapped into LDAP common name attribute. For example if some LDAP object class uses cn as its name, it should be mapped into objectName.

Node node. This node holds definition for single P2J to LDAP node. Configuration file should contain separate node node for each node to node mapping.

Attribute
Description
ldap
LDAP node full name. The name may contain asterisk (*) at the end. In this case entire subtree will be scanned recursively and mapped into appropriate P2J subtree automatically.
p2j
P2J node name.

Note:
if class and node mapping information is omitted that mapping is generated automatically from the P2J schema file specified in the configuration.

Command Line Format

Utility accepts following command line:

java LdapMapGen <source_map.xml>  [-o <output_map.xml> | -u] [-s <ldap.schema>]


Parameter
Description
<source_map.xml> Source configuration/mapping file
-o <output_map.xml> File name of the output file where generated mapping will be stored
-u   Write mapping into LDAP directory instead of file name
-s <ldap.schema> Generate LDAP schema file

Note: Options -o <output_map.xml> and -u are mutually exclusive.

Known Limitations

  1. Utility can connect LDAP server only using ordinary connection, TLS connection is not supported.
  2. LDAP schema can be generated only if mapping is generated from P2J schema.xml file. Also, see note.
  3. Storing mapping in the LDAP supports storing only schema-level mapping.

Appendix C. DirectoryEdit Utility

General Description

The DirectoryEdit utility is a simple text mode interactive menu driven tool which allows to browse and edit P2J directory. The utility presents a set of menus and user enters commands with optional parametpackage.htmlers to perform operations. When this is necessary utility may request additional input from the user.

Menu Description

Utility starts its work in Main Menu (Top Level Menu) :

Menu item key and parameters
Menu item text
Detailed description
.
Print menu Just print menu.
b node
Open batch for node This command opens batch editing session for node node. If after space no node name is provided then batch is opened for root node. Upon successful start of batch editing session utility automatically opens Node Level Menu for the node node.
b
Back to batch node This command can be used to switch back to node level menu if batch editing session is already open.
c
Commit batch This command commits active editing session and updates directory with collected changes. Regardless from the result (success or failure) current batch editing session is closed.
r
Rollback batch This command rollbacks active editing session and throws out collected changes. Regardless from the result of the operation current batch editing session is closed.
q
Quit
Quit application. If there is active editing session then quit is not allowed, changes must be committed or rolled back.

Successful opening of the batch editing session or manual switching back to node level menu (see description of b command above) opens Node Level Menu:

Menu item key and parameters
Menu item text
Detailed description
.
Print menu Just print menu.
l
List attributes and nodes Print detailed list of node attributes and child nodes.
t
List attribute definitions Print definitions of all attributes defined for the current node object class.
E node
Edit node Open Node Level Menu for child node node. New node path is set to current node path with appended node. This does allow to go deeper through hierarchy of the nodes.
C node [class]
Create child node Create child node node of class class. If class is omitted it is requested separately. If object class defines attributes then interaction with the user is started. For all mandatory attributes values are requested. Then user can add values to the attributes (both, mandatory and ordinary).
D node
Delete child node Remove node node.
M node1 node2
Move node Move node1 to node2. Parameter node1 always assumes node relative to the current location. Parameter node2 can represent absolute node name or node name relative to current location. If first character of node2 is slash  then absolute path is assumed.
e attribute
Edit node attribute This command opens Attribute Level Menu for the attribute attribute of current node.
c attribute Create node attribute This command creates attribute or adds new value to the existing multi-valued attribute.
d attribute Delete node attribute This command removes attribute attribute.
q
Quit Return to previous menu. If current menu was entered from Top Level Menu then control is returned to Top Level Menu. If current menu was entered from Node Level Menu of parent node then control is returned to that menu.

Attribute editing command opens Attribute Level Menu:

Menu item key and parameters Menu item text Detailed description
.
Print menu
Just print menu.
l
List values List all values of the attribute.
e n
Edit value n Change value n of the attribute.
d n
Delete value n Delete value n from the attribute.
a
Add new value Add new value to the attribute.
q
Quit Return to Node Level Menu.


Appendix D. Directory Utilities

The directory package offers a few command line utilities:

WARNING!
Except the diff, all other utilities change the specified directory file. Please make it a rule to create a backup copy of the file before applying any of the utilities.

Using diff

Use diff to compare the two directory files or branches of the directory files. The output can be redirected to a file for analysis. The diff utility compares the contents of the directories, which does not depend on the order of sections in the xml file.
How to run diff
java com.goldencode.p2j.directory.DirectoryDiff
Parameters
left-dir.xml right-dir.xml [ path | left-path [ right-path] ]

You have to specify two directory files. With no other parameters, the whole directories will be compared. You can also give a path. Then this directory path will specify a branch of the directories to compare. You can also specify different paths for the first and the second directory and only those branches will be compared.

The output will be self-explainable. The first directory is referred to as the Left directory, The second directory is referred to as the Right directory.

Using copy

Use copy to copy a branch of the input directory to the specified copy point of the output directory. Copy utility does not synchronize directory nodes, however. If the output file contains something, it will be preserved, not removed.
How to run copy
java com.goldencode.p2j.directory.DirectoryCopy copy
Parameters
<source-xml> <source-path> <target-xml> <target-path>

All four parameters are required. The target-xml may specify the same file as the source-xml, but be careful to specify the non-overlappping branches in this case!
The target file will be created if it doesn't exist.
 

Using remove

Use remove to remove a branch from a directory.
How to run remove
java com.goldencode.p2j.directory.DirectoryCopy remove
Parameters
<target-xml> <target-path>

The meaning of the parameters is obvious.

Using clear

Use clear to scan the specified branch of a directory file for all nodes of a specified directory class, and then remove either the whole attribute specified by its name or a matching value of the attribute.
How to run clear
java com.goldencode.p2j.directory.DirectoryCopy clear
Parameters
<target-xml> <target-path> node-class attr-name [attr-value]

The target-xml directory file gets scanned recursively starting from the target-path. All nodes of the node-class are inspected. If attr-value is not specified, the attribute attr-name is removed unconditionally regardless of its current value or values. If the attr-value is specified, then only that value will be removed. If the attrbute is multivalued, the other values remain untouched; otherwise the attribute is removed.
Examples
To clear the protected="false" attribute from all user accounts in test-directory.xml, type:

java com.goldencode.p2j.directory.DirectoryCopy clear test-directory.xml /security/accounts/users user protected false

To clear account bogus from all ACLs of the same directory file, type:

java com.goldencode.p2j.directory.DirectoryCopy clear test-directory.xml /security/acl strings values bogus

To clear all user accounts from the alias attribute, type:

java com.goldencode.p2j.directory.DirectoryCopy clear test-directory.xml /security/accounts/users user alias

Using set

Use set to add a value for an attribute or set the existing value to a given one for all nodes of the specified directory class in a branch of a directory file. When setting the existing atrribute, you can specify the index at which the value will be set. This is a conditional operation and it will be a no-op if there are less values than the index implies (index n is valid if there are at least n values).
How to run set
java com.goldencode.p2j.directory.DirectoryCopy set
Parameters
<target-xml> <target-path> node-class attr-name attr-value [value-index]
The target-xml directory file gets scanned recursively starting from the target-path. All nodes of the node-class are inspected. If value-index is not specified, the attribute attr-name gets the attr-value added. If the value-index is specified, then that value will be replaced if there are enough values. If the attrbute is multivalued, the other values remain untouched.
Examples
To set the enabled="true" attribute for all process accounts, type:

java com.goldencode.p2j.directory.DirectoryCopy clear test-directory.xml /security/accounts/processes process enabled true



 
Copyright (c) 2005-2010, Golden Code Development Corporation.
ALL RIGHTS RESERVED. Use is subject to license terms.
Skip navigation links
Copyright (c) 2004-2017, Golden Code Development Corporation.
ALL RIGHTS RESERVED. Use is subject to license terms.