LDAP (Lightweight Directory Access Protocol) is a binary protocol used to
communicate with LDAP servers. LDAP servers are databases which work
using a tree structure to store data, rather than rows and tables.
They provide a service, something like a telephone directory, allowing
information to be made available about people or other things (computers,
printers, or anything really) within the servers domain.
The most common LDAP server on Windows is the Windows Active Directory
server. There are however other LDAP server you may need to connect to.
Windows Active Directory is an example of an LDAP server, not the only LDAP
server.
Active Directory Directory Services (ADDS) is a really big deal for larger
organizations which need to administer large groups of
people and computers. It is a centralized system that allows for security and permissions to be set in one place, which affect the larger
organization.
This section focuses on interacting with an Active
Directory server.
Aside: If you need
to brush up on Active Directory Directory Services because your client wants
you to support it, but you know nothing about it then I recommend this
YouTube playlist. Start with item 5 on that list. It's an excellent
introduction to Active Directory.
A good example of these features in action is in the
\examples\nettalk\LDAP\ActiveDirectory folder.
MessageID
Communications between the server and your
program is asynchronous.
This means that you made a call to one
of the methods (ValidateUser, UserInGroup etc) a MessageID is
returned. You should store this ID for use when results arrive.
When a response to a message is received from the server then the
.Done method will be called.
When inspecting the results in the .Done method, the
ID of the message being replied to is passed into the method as
a parameter. You can use the value stored earlier to determine which
message is being replied to.
You do not need to wait for a
response before sending another message.
Validating a User Login and Password
You can check to see if a user login and password are valid by using the ValidateUser method.
The user login value is not case sensitive. Can be either the login
value Or the Display Name.
Example of
calling the method
net.Host = pParms.pHost
net.Port = pParms.pPort
net.SetDomain(pParms.pDomain)
net.AuthUser
= pParms.pAuthUser
net.AuthPassword = pParms.pAuthPassword
MessageId = net.ValidateUser()
Remember the call is
asynchronous, so the answer will be provided in either the
.Done method or the .ErrorTrap
method.
Add the following code to the .Done
method to get the result.
If pMessageId =
MessageId
ans = self.ValidUser()
End
User Filters
When calling subsequent functions, a User Filter is often used to
identify the user you are reading information about. This allows you to
identify a user based on any of their (unique) properties. For example;
cn=Wilson S. Demaggioor
userPrincipalName=wilson@text.local
Hint: The
userPrincipalName is the name of
the login user attribute.
Complex Filters
The above example
is a simple expression. However more complicated search expressions
are possible. The syntax of the expression is fairly simple, but may
be different to the syntax you are used to.
OR (|)
You can do a search using two conditions by using the OR
operator. The symbol for this operator is the pipe symbol (|). the
general syntax of the string is
operator(expression)(expression)So, if we wanted to
search for all users with the surname (sn) of Gates or Jobs, the
expression would look like this;
|(sn=Gates)(sn=Jobs)
Each expression in the above search could in turn be a complex
expression.
AND (&)The AND operator
is represented by the & symbol. A search for all users with the
surname (sn) of Jobs and the first name (givenName) of Steve would
look like this;
&(sn=Jobs)(givenName=Steve)
As mentioned before each expression in the above could in turn
be a complex expression. Let's search for all the users with surname
Jobs, where the first name is Steve or Steven.
&(sn=Jobs)(|(givenName=Steve)(givenName=Steven))
some LDAP clients require the whole filter to be wrapped in
brackets, so the above would become
(&(sn=Jobs)(|(givenName=Steve)(givenName=Steven)))
NetTalk will handle this fine, but does not require it.
There are many resources on the web for understanding how LDAP
search terms are constructed. If you want (or need) to make more
complex searches then the following material is recommended;
https://technet.microsoft.com/en-us/library/aa996205(v=exchg.65).aspx
Get Attributes for one (or more) Users
One of the primary uses of Active Directory is to store information
about users. Therefore NetTalk LDAP has a
GetAttributes method that
allows you to read some, or all, the attributes for one (or more) users.
The
User parameter is constructed as
a
user filter.
The
Attributes
parameter is a comma separated list of the attributes to fetch.
If all the attributes should be fetched then set this to
'*'.
Example of calling the method
net.AuthUser = pParms.pAuthUser
net.AuthPassword
= pParms.pAuthPassword
net.Host = pParms.pHost
net.Port =
pParms.pPort
net.SetDomain(pParms.pDomain)
net.SetAttributesQueue(AttributesQueue)
MessageId =
net.GetAttributes(pParms.pUser,pParms.attributeList)The
list of attributes is placed into a queue - the queue set using the
SetAttributesQueue method.
Each attribute name is placed into the
first field in the queue, and the value into the second field.
If
SetAttributesQueue is not called, then an internal queue of the object,
called
AttributesQueue will contain the
result.
Another method,
GetAttributeValue, allows you to easily retrieve a value from the
queue.
Checking if a User is in a Specific Group
A really common request we get is for our program to authenticate a user
against active directory before allowing them to access the program. On the
administrator (server) side users can then be denied or granted access
based on them being in, or out, of a known group.
This can be
done in addition to a program security system (like Secwin, or Super
Security) or it may operate as a simple login system of its own.
For example;
- On the server side the administrator creates a specified group
called (for example) AmazingAccounts. (In your case, set the name to
something related to your product.)
- On the server side the Administrator creates Users (or more
likely already has Users created for each person already.)
- On the server side the Administrator can control who has access
to the application by simple placing users in the AmazingAccounts
group. If a user is terminated they can be removed from the
AmazingAccounts group very easily.
If you are using this in conjunction with another system then in your
program you add a test AFTER the current login is successful, but BEFORE
the program menu appears. This test queries the ADDS server with
something like "Is WhateverUser in the AmazingAccounts group?". If he
is, then keep going, if not, deny access.
If you are using this
as your only login system then you can just create a simple window to
collect the user name and password from the user. You can then pass
these through to the Active Directory Server to see if they are valid.
If they are then test to see if the user is in the required group.
It's
possible to prime the login field with the Windows Login, but you will
need to retrieve the password from the user.
The validation here takes 3 parameters of
interest. The
AuthUser and
AuthPassword are the same as the ones used for
the
ValidateUser method.
The
User parameter is constructed as
a
user filter.
The
InGroup
parameter is the name of the group to compare
against.
Example of calling the method
net.AuthUser = pParms.pAuthUser
net.AuthPassword
= pParms.pAuthPassword
net.Host = pParms.pHost
net.Port =
pParms.pPort
net.SetDomain(pParms.pDomain)
MessageId =
net.UserInGroup('cn=' & pParms.pUser,pParms.pInGroup)Again the
result will appear in the
.Done method
if pMessageId = MessageId
ans =
self.UserIsInGroup
post(event:closeWindow)
end
LDAP uses a binary protocol to talk between the server and the client. This can make
the traditional debugging approach (of simply inspecting outgoing
packets, and incoming replies) difficult.
DebugPackets
To solve this problem
the NetLDAP class has a property called DebugPackets.
This is set to
false by default.
If it is set to true then packets going out, and
coming in, are converted to a text format before sending them to
debugview.
Of course viewing the protocol as text is useful only
to the degree to which you understand the protocol, however it certainly
makes it a lot easier to see the server response, and hopefully
interpret any errors which may be coming from the server.
OpenLDAP
Testing the client side of the connection is only half the problem.
If the server is misconfigured, or not accessible at all, then all
the changes on the client side won't help. Therefore it is useful to
have a command line tool (or tools) which can be used as a reference
point - something you can use to connect to a server to determine
that the server is working correctly. There are a variety of tools
available. The one we use internally are tools supplied by the
OpenLDAP
project.
The OpenLDAP install can be downloaded from
https://www.openldap.org/software/download/ .
Once
installed (typically to
c:\LDAP) the
client tools can be found in the
c:\LDAP\ClientTools
folder.
Example
Here is a typical request using
ldapsearch
ldapsearch -x -LLL -E pr=200/noprompt
-h 192.168.2.164 -p 389 -D "wilson@test.local" -w "NetTalk10" -b "cn=users,dc=test,dc=local"
-s sub "(sn=Demaggio)" cn mail sn userPrincipalName phone
In the above command;
192.168.2.164
is the name of the server being connected to
389 is the port
number
wilson@test.local is the user
Logon
NetTalk10 is the password
dc=test,dc=local
is the domain. Note the rather unusual way of showing this. If the
domain was say capesoft.co.za then this would be
dc=capesoft,dc=co,dc=za
Demaggio is
the surname (last name) of the user we are getting attributes for.
You could use a
* here to get the attributes for all
users.
cn mail sn userPrincipalName phone
are the attributes being fetched. Use a
* here for all
attributes.
Here is another example
ldapsearch -x -LLL -E pr=200/noprompt -h
capesoft.co.za -p 389 -D "wilson@test.local" -w "NetTalk10" -b "cn=users,dc=capesoft,dc=co,dc=za"
-s sub "(sn=*)" *