Wednesday, March 18, 2009

Object Pascal & LDAP

One common feature of enterprise software is security, applications must be prepared to allow/deny access to its resources (modules or dialogs) to different usesers. The vast majority of development teams, every time a project involves user authentication develops its own methods based on a repository (database) of user accounts. This approach isn't bad at all, but implies in some cases the duplication of users, one user account for the app, another with the same name for the operating system session, anoter one for email.

To resolve the problem described above, Directory Services were created, basically they are a common database of users and groups shared across a computer network allowing its access from different applications. Windows servers (2000 and 2003) call it Active Directory, UNIX and Linux uses LDAP and OpenLdap.

Personally, I preffer open standards, and the fact that Ms Active Directory can cooperate transparently with an LDAP server favors the later because it can be installed and accesed by Windows, Linux, and other Oses.

In this article, I'll show how to set up a simple OpenLDAP server and a client that works on Delphi or FreePascal with the help of the great Synapse library.

Installing OpenLdap

First, download the OpenLdap binaries from Win32 port or you can look at this link for other operating systems. Of course you can download the sources and build an OpenLdap server from scratch.

From here, all examples will be based on the Win32 version of the server, but I'm sure you'll understand how to adapt the examples and configuration to a Linux based server.

After installing and configuring slapd, the OpenLDAP daemon, I'll change the default settings. Stop the OpenLDAP Directory Service going to Control Panel -> Administrative Tools -> Services in windows and edit the slapd.conf file replacing the default entries with:
ucdata-path ./ucdata
include ./schema/core.schema
include ./schema/cosine.schema
include ./schema/inetorgperson.schema

suffix "dc=ldapserver,dc=com"
rootdn "dc=ldapserver,dc=com
rootpw secret

Save and restart the service.

The next step you must do is the creation of a root entry in the LDAP database, this can be done by creating an ldif file like this:
dn: dc=ldapserver,dc=com
objectClass: top
objectClass: dcObject
objectClass: domain
dc: ldapserver
o: MyLdapServer, Inc.

save the file as root.ldif and add to the database using this command from the command prompt:
ldapadd -D "dc=ldapserver,dc=com" -W -f root.ldif

The next step is the creation of a group of users. To accomplish this, I'll create the users.ldif file:

dn: ou=users,dc=ldapserver,dc=com
objectClass: top
objectClass: organizationalUnit
ou: users

To add this group use this statement:
ldapadd -D "dc=ldapserver,dc=com" -W -users.ldif

Now, I must populate the database with user and group entries using an ldif file like this one:
# User leonardo
dn: cn=Leonardo M. Rame,ou=users,dc=ldapserver,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: leonardorame
cn: Leonardo M. Rame
gn: Leonardo M.
sn: Rame
ou: users
mail: martinrame@yahoo.com
userPassword: lrame123

# User Peter
dn: cn=Peter Jones,ou=users,dc=ldapserver,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: peterjones
cn: Peter Jones
gn: Peter
sn: Jones
ou: users
mail: peterjones@yahoo.com
userPassword: peter111

Save the file as leonardo.ldif and add the user using this command:
ldapadd -D "dc=ldapserver,dc=com" -W -f leonardo.ldif

If you whant to check if the entries where added correctly, you can try this:
ldapsearch -b "dc=ldapserver,dc=com" "objectclass=*" -x


Now, I'll create two groups of users, Full Access and Limited. The first, has access to all features of the system, the second one, has a limited vision. Let's create this groups.ldif file:
# Groups
dn: ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups

# Full Access group
dn: cn=Full Access,ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: groupOfNames
cn=Full Access
memberuid: leonardorame

# Limited Access group
dn: cn=Limited,ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: groupOfNames
cn=Limited
memberuid: peterjones

Save the file as leonardo.ldif and add the user using this command:
ldapadd -D "dc=ldapserver,dc=com" -W -f leonardo.ldif

If you whant to check if the entries where added correctly, you can try this:
ldapsearch -b "dc=ldapserver,dc=com" "objectclass=*" -x

Now, I'll create two groups of users, Full Access and Limited. The first, has access to all features of the system, the second one, has a limited vision. Let's create this groups.ldif file:
# Groups
dn: ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups

# Full Access group
dn: cn=Full Access,ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: groupOfNames
cn=Full Access
memberuid: leonardorame

# Limited Access group
dn: cn=Limited,ou=groups,dc=ldapserver,dc=com
objectClass: top
objectClass: groupOfNames
cn=Limited
memberuid: peterjones

of course, you must type:
ldapadd -D "dc=ldapserver,dc=com" -W -f grouops.ldif

With that, I finished the server configuration part. Now, I must code the program to access the Ldap server:
program ldapexample;

{$APPTYPE CONSOLE}

uses
SysUtils,
classes,
lDapSend; // <-- Synapse Ldap unit


function CheckAccess(ALevel, AUserName, APassword: string): Boolean;
(* This function checks if a user has access to a group
in the ldap server *)
var
ldap: TLDAPsend;
lAttribs: TStringList;
I: Integer;
A: Integer;
lMember: string;
begin
Result := False;
ldap:= TLDAPsend.Create;
lAttribs := TStringList.Create;
try
ldap.TargetHost := 'localhost';
(* Anonimous access *)
ldap.Login;
if ldap.Bind then
begin
lAttribs.Add('member');
ldap.Search('cn=' + ALevel + ',ou=groups,dc=ldapserver,dc=com',
False, '', lAttribs);
if ldap.SearchResult.Count = 1 then
begin
(* Iterate through members of this group *)
for I := 0 to ldap.SearchResult[0].Attributes.Count - 1 do
begin
(* search for this member attributes *)
lAttribs.Clear;
lAttribs.Add('uid');
lAttribs.Add('userPassword');
lMember := ldap.SearchResult[0].Attributes[I][0];
ldap.Search(lMember, False, 'uid=' + AUserName, lAttribs);
if ldap.SearchResult.Count = 1 then
begin
(* if found, iterate through its attributes to find uid and userPassword *)
for A := 0 to ldap.SearchResult[0].Attributes.Count - 1 do
if ldap.SearchResult[0].Attributes[A].AttributeName = 'userPassword' then
if ldap.SearchResult[0].Attributes[A][0] = APassword then
begin
Result := True;
break; // for A
end;
break; // for I
end;
end;
end;
end
else
(* Display ldap error message *)
Writeln(ldap.ResultString);
(* Disconnect from ldap server *)
ldap.Logout;
finally
ldap.Free;
lAttribs.Free;
end;
end;

begin
if CheckAccess('Full Access', 'leonardorame', 'lrame123') then
writeln('User authenticated Ok.')
else
writeln('Wrong user.');

if CheckAccess('Limited', 'leonardorame', 'lrame123') then
writeln('User authenticated Ok.')
else
writeln('Wrong user.');

if CheckAccess('Limited', 'peterjones', 'peter111') then
writeln('User authenticated Ok.')
else
writeln('Wrong user.');

end.

No comments: