directory-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jörg Henne <j.he...@levigo.de>
Subject openthinclient.org Contributions for LDAP mapping and DHCP services
Date Mon, 17 Dec 2007 19:27:08 GMT
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body bgcolor="#ffffff" text="#000000">
Hi all,<br>
<br>
as discussed in the LDAP mapping thread, we have two potential
contributions for the Apache Directory Project: a small object to LDAP
mapping library and a PXE server software which could form the basis
for a full DHCP server implementation. In order to get this going, I'll
give you a quick wrap up of what the state of the components is. In the
next few days I'll then extract the relevant stuff from our code-base
and make it available in the Apache DS sandbox.<br>
<br>
1. The LDAP mapper<br>
----------------------<br>
The LDAP mapper strives to achieve for LDAP what Hibernate, JPA, et.
al. do for relational databases: map Java objects to LDAP objects in a
transparent fashion. For an overview about what it can and can not do,
I'll just refer you to my previous message. Instead, I'll give you a
rough idea of how using the LDAP mapper looks like. <br>
<br>
Just as using an ORM leaks some details of the underlying persistence
mechanism into the domain model, namely having to deal with the object
identity and the difference between relational and object oriented
association semantics, one has to pay some tribute to the way LDAP
works when implementing persistent objects: every LDAP-persistent class
must have an attribute holding the DN and every class must have an
attribute for the RDN. In our data model all classes inherit from
DirectoryObject which has a dn attribute, but that isn't a requirement.<br>
<br>
Each domain model class maps to a particular combination of LDAP object
classes. Since the mapping layer supports polymorphic results (well,
you can't just restrict what objects are referenced in a group under
LDAP), the mapping has to be able to determine the domain class for an
LDAP object. For those cases, the mapper currently requires exactly one
"key class" which is an LDAP object class it expects to be unique for a
given domain object class.<br>
<br>
Access to object properties is currently done using simple property
getter/setter access. No fancy bytecode enhancement or private field
access. Sorry.<br>
<br>
Now, lets consider we have a domain class for users which maps to
inetOrgPersons in LDAP:<br>
<blockquote>public class User extends DirectoryObject {<br>
    private Set&lt;UserGroup&gt; userGroups;<br>
  <br>
    private Set&lt;ApplicationGroup&gt; applicationGroups;<br>
    private Set&lt;Application&gt; applications;<br>
    private Set&lt;Printer&gt; printers;<br>
  <br>
    private Location location;<br>
  <br>
    private String sn;<br>
    private String givenName;<br>
    private byte[] userPassword;<br>
    private String newPassword = "";<br>
    private String verifyPassword = "";<br>
  <br>
    private Integer uid;<br>
[...]<br>
[getters/setters for the properties above]<br>
</blockquote>
The DirectoryObject super-class provides for the dn and description
attributes:<br>
<blockquote>public abstract class DirectoryObject implements
Serializable {<br>
  private static final long serialVersionUID = 1L;<br>
  <br>
  private String dn;<br>
  private String name; // most objects have a name, so it is also here
- we're lazy.<br>
  private String description;<br>
</blockquote>
This object might be mapped to the directory using the following
descriptor:<br>
<blockquote>    &lt;class name="org.openthinclient.common.model.User"
base-rdn="ou=users"<br>
       
filter="(&amp;amp;(objectclass=person)(!(objectclass=computer)))"<br>
        object-classes="top, person, organizationalPerson,
inetOrgPerson" key-class="person"&gt;<br>
        &lt;dn-attribute name="dn" type="java.lang.String" /&gt;<br>
        &lt;rdn-attribute name="cn" get-method="getName"
set-method="setName" /&gt;<br>
  <br>
        &lt;attribute name="description" type="java.lang.String" /&gt;<br>
        &lt;attribute name="sn" type="java.lang.String" /&gt;<br>
        &lt;attribute name="givenName" type="java.lang.String" /&gt;<br>
        &lt;attribute name="userPassword" type="[B" /&gt;<br>
        &lt;attribute name="uid" type="java.lang.Integer" /&gt;<br>
  <br>
        &lt;many-to-one name="l"
type="org.openthinclient.common.model.Location"
get-method="getLocation" set-method="setLocation" /&gt;<br>
        &lt;many-to-many name="applicationGroups"
type="org.openthinclient.common.model.ApplicationGroup"<br>
            filter="(uniqueMember={0})" member-field="uniqueMember"
/&gt;<br>
        &lt;many-to-many name="applications"
type="org.openthinclient.common.model.Application"
filter="(uniqueMember={0})"<br>
            member-field="uniqueMember" /&gt;<br>
        &lt;many-to-many name="printers"
type="org.openthinclient.common.model.Printer"
filter="(uniqueMember={0})"<br>
            member-field="uniqueMember" /&gt;<br>
        &lt;many-to-many name="userGroups"
type="org.openthinclient.common.model.UserGroup"
filter="(uniqueMember={0})"<br>
            member-field="uniqueMember" /&gt;<br>
    &lt;/class&gt;<br>
</blockquote>
As you can see, you have to tell the mapping<br>
- the Java and LDAP class names<br>
- the key-class (in cases where the mapping isn't told the type of
domain class otherwise, it will use this key class to determine the
Java class for a given LDAP object)<br>
- the name of the DN attribute<br>
- the name of the RDN attribute (this is semi-redundant, but required
in the current design)<br>
- mappings for the attributes. Currently Strings, ints, and byte-arrays
are supported<br>
- mappings for associations. In our case, there is a many-to-one
association for the location and several many-to-many associations for
group memberships. The "filter" attribute of the many-to-many mapping
should probably be optimized away, since it is redundant in almost all
cases.<br>
- the base-rdn is the name relative to the base DN where an object will
be saved if no explicit dn of a container is specified. In the default
directory layout of openthinclient.org objects are nicely sorted into
separate ous. The rationale behind this would lead too far for now.
I'll elaborate on this some time later.<br>
<br>
Now let's suppose we want to do something with this mapping. The first
thing we need is an LDAP connection descriptor. This is basically a
fancy way of specifying a JNDI environment:<br>
<blockquote>LDAPConnectionDescriptor lcd = new
LDAPConnectionDescriptor();<br>
lcd.setPortNumber(10389);<br>
lcd.setProviderType(ProviderType.SUN);<br>
lcd.setBaseDN("dc=test,dc=test");<br>
lcd.setHostname("foo");<br>
lcd.setCallbackHandler(handler); // JAAS callback handler queried when
credentials are needed<br>
</blockquote>
Next, we need an instance of the Mapper itself. We create it, by
loading a mapping specification (xml file):<br>
<blockquote>Mapping m = Mapping.load(new
getClass().getResourceAsStream("myLDAPMapping.xml");<br>
m.initialize();<br>
m.setDirectoryFacade(lcd.createDirectoryFacade()); // some API-cruft
still here...<br>
</blockquote>
Mapping.initialize() has to be called manually, since
openthinclient.org also supports hybrid mappings where one part of the
domain model resides in Apache DS and another part (Users!) is pulled
from a MS-ADS. After constructing a hybrid mapping, both directories
transparently appear as one. Or at least, that's the theory. In
practice some awkwardnesses are hard to abstract away.<br>
<br>
Now's the time to create a new user and assign him/her to a group:<br>
<blockquote>User user = new User();<br>
user.setName("jdoe");<br>
user.setGivenname("John");<br>
user.setSn("Doe");<br>
user.setUid(1234);<br>
  <br>
m.save(user); // make user persistent. Will automatically be saved to
cn=jdoe,ou=users,...<br>
  <br>
UserGroup group = new UserGroup();<br>
group.setName("Fictional users");<br>
group.getMembers().add(user);<br>
  <br>
m.save(group);<br>
</blockquote>
Thanks to transitive persistence, this could also be done in one go:<br>
<blockquote>User user = new User();<br>
...<br>
UserGroup group = new UserGroup();<br>
group.getMembers().add(user);<br>
...<br>
m.save(group); // saves user as well!<br>
</blockquote>
Now, finally some examples for loading/querying:<br>
<blockquote>User user = m.load(UserGroup.class, "cn=jdoe,ou=users,...");<br>
Set&lt;User&gt; allUsersInDefaultSubtree = m.list(User.class);<br>
Set&lt;User&gt; someUsers = m.list(User.class, new Filter("(sn=D*)"),
SearchScope.SUBTREE);<br>
</blockquote>
Ok, so this should give you a rough idea about the LDAP mapping
component. As I said, the component is rather simple (a mere 19
classes) and still has some deficiencies (as mentioned in the earlier
post), however, it gets the job done of mapping our rather complex
domain model.<br>
<br>
2. the PXE proxy.<br>
------------------<br>
Currently we don't do full DHCP, i.e. users are supposed to supply IP
addresses to thin clients using their existing DHCP infrastructure.
I'll get to that later. PXE piggy-backs on the DHCP protocol, to
support booting of diskless clients. For a rough overview, Wikipedia
has this: <a class="moz-txt-link-freetext" href="http://en.wikipedia.org/wiki/Preboot_Execution_Environment">http://en.wikipedia.org/wiki/Preboot_Execution_Environment</a><br>
<br>
For the PXE proxy (I think the term "proxy" is inadequate for the PXE
server, but that's what most people call it) this means that it has to
listen for DHCP requests from PXE-enabled clients (detected from
vendor-ID) and "inject" a special type of DHCP offer when one is
detected. The boot-information is then transferred using a separate
exchange using DHCP on a separate port (4011 instead of 67/68).
Implementing a PXE service is quite a bit simpler than implementing
full DHCP since a PXE proxy usually doesn't need to manage client state
as a DHCP server does.<br>
<br>
Although we currently only do PXE, protocol-wise we already have most
of what DHCP needs. The state of the DHCP code as we picked it up
wasn't there yet, as far as I remember. The real challenge of
implementing DHCP comes from some limitations of the Java networking
API, though. DHCP talks to clients which don't have an IP address yet.
Therefore the initial exchange takes place via UDP broadcast. Serving
clients with an IP address, requires knowledge about the physical
network (or vLAN) on which the device resides. Acquiring this piece of
information is harder than what one would expect. We have a summary
here <a class="moz-txt-link-freetext" href="https://issues.openthinclient.org/otc/browse/SUITE-39">https://issues.openthinclient.org/otc/browse/SUITE-39</a>,
but the
bottom-line after doing quite some research is that there is no
solution that works on every system:<br>
- For Windows the solution is to bind to each network interface
individually. Broadcasts are received by the interface bound to the
address corresponding to the primrary IO address of the network
interface. Nice, bit not RFC compliant.<br>
- For single-homed systems, one can weasel out of the problem by just
using a static configuration for a single LAN segment. <br>
- UNIX systems only receive broadcasts for sockets bound to the 0.0.0.0
address, but doing so loses information about the network from which
the request arrived. Currently I only see using raw sockets as a
feasible solution. Either through
<a class="moz-txt-link-freetext" href="http://www.savarese.org/software/rocksaw/">http://www.savarese.org/software/rocksaw/</a>
or - even more crowbar-style
- JPCAP <a class="moz-txt-link-freetext" href="http://netresearch.ics.uci.edu/kfujii/jpcap/doc/">http://netresearch.ics.uci.edu/kfujii/jpcap/doc/</a>.
Both require
some native code, unfortunately. And, of course, a corresponding MINA
IoAcceptor driver and raw IP protocol handlers. *sigh*<br>
<br>
Since openthinclient.org only needs to do PXE for clients served by an
existing DHCP server, we used the sneaky solution of not listening for
the DHCP request, but for the RESPONSE. That way we can easily learn
the address assigned to the client. :-)<br>
<br>
So much for today. <br>
<br>
Joerg Henne<br>
</body>
</html>

Mime
View raw message