Sunday, February 25, 2007

 

Object Pascal & LDAP


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.

Comments:
Por que escribes en inglés si no sabes inglés?
 
Thanks for the example. Just one remark - your checkAccess function probably won't work for non-plaintext passwords, you'd better use non-anonymous bind and see if it succeeded.
 
I found this site using [url=http://google.com]google.com[/url] And i want to thank you for your work. You have done really very good site. Great work, great site! Thank you!

Sorry for offtopic
 
You are welcome Anonymous.
 
Under Unix, you will need to use ldappasswd to set a password instead of using secret in the slapd.conf file.

For an install from source, the default location for the slapd.conf file is /usr/etc/openldap/slapd.conf
 
The ldif file for doing the add group appears twice and has two bugs, below should be the correct implementation:

# 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
cn: Full Access
objectClass: top
objectClass: groupOfNames
member: uid=leonardorame

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


The difference is the "member:" instead of "memberuid:" and "uid=" in front of the username in the "member:" row. second, the "cn=" is supposed to be "cn: ".

Note sure why the old/current document is wrong, but the changes above were required before I could get the script to run.
 
The ldapexample.pas program should have the directive

{$MODE delphi}

added below the APPTYPE to get the application to compile without errors.
 
Post a Comment

Links to this post:

Create a Link



<< Home

This page is powered by Blogger. Isn't yours?