PDA

View Full Version : CanConnect?


miha
Sep 12th, 2006, 10:48 AM
Hello!

How does one, using ldapTemplate, go about checking if a single DN can connect to a ldap server using a provided password?
Something along the line of:

canConnect(LdapUser user, String password)

or something like that, returning boolean (or anything descriptive enough :))?

Thanks!

miha
Oct 17th, 2006, 04:53 AM
I've been working with LdapTemplate quite a bit since i've posted my question, but still havent found anything that would be helpfull.
Does this mean that ldaptTemplate does not have such a method??

Thanks in advance

rasky
Oct 17th, 2006, 07:25 AM
There is no such method in Spring LDAP. For authentication we recommend Acegi Security, which includes good support for LDAP authentication.

It would of course be possible to implement this type of functionality using Spring LDAP, but since there is another Spring Framework sub project providing that funcionality we decided not to include it, at least not at the time being.

If you still want to implement it yourself using Spring LDAP you can get some useful tips from the code in Acegi.

mlarchet
Oct 17th, 2006, 08:41 AM
I've been in the same trouble as you are.
Here is my solution :
public boolean checkPassword(String login, String password) {
log.debug("LdapServiceDao::checkPassword()");

// Construction du DN
DistinguishedName dn = new DistinguishedName("ou=People,dc=univ,dc=fr");
dn.append(new DistinguishedName(getUserDn(login)));

// Connexion manuelle
LdapContextSource ctxSource = new LdapContextSource();
ctxSource.setUrl(url);
ctxSource.setUserName(dn.encode());
ctxSource.setPassword(password);
ctxSource.setPooled(false);
try {
ctxSource.afterPropertiesSet();
ctxSource.getReadWriteContext();
return true;
}
catch(Exception e) {
return false;
}
}


The only problem is dependancy injection is broken, I have to manually construct a special LdapContextSource for each authentication.
This LdapContextSource is no longer used after authentication for any operation.

rasky
Oct 17th, 2006, 09:05 AM
The general idea looks ok, but I think you need to perform an operation on the context in order for the actual authentication to take place. I would suspect your implementation will always return true. I might be wrong, but I think that's the way it works.

miha
Oct 18th, 2006, 05:37 AM
I belive that this is what i was looking for! Will try it out!
Thank you very very much!

mlarchet
Oct 18th, 2006, 07:37 AM
The general idea looks ok, but I think you need to perform an operation on the context in order for the actual authentication to take place. I would suspect your implementation will always return true. I might be wrong, but I think that's the way it works.

I'm really sure that it doesn't always return true, otherwise my application should be in trouble and it's not the case...

ulsa
Oct 18th, 2006, 08:09 AM
The general idea looks ok, but I think you need to perform an operation on the context in order for the actual authentication to take place. I would suspect your implementation will always return true. I might be wrong, but I think that's the way it works.

I just verified that simply creating a context is enough to authenticate a user. It might be provider-specific, though. I tested towards a SunONE Directory Server 5.2.

miha
Oct 19th, 2006, 04:30 AM
The general idea looks ok, but I think you need to perform an operation on the context in order for the actual authentication to take place. I would suspect your implementation will always return true. I might be wrong, but I think that's the way it works.


I've tested mlarchet's method with IBM Tivoli, and it works perfectly!
If I supply a non-existing user, I get:


org.springframework.ldap.UncategorizedLdapExceptio n: Operation failed;
nested exception is javax.naming.AuthenticationException:
[LDAP: error code 49 - Invalid Credentials]

which subsequently leads to

return false;

which is what I need.

Thanks again!

rasky
Oct 19th, 2006, 09:21 AM
I've tested mlarchet's method with IBM Tivoli, and it works perfectly!
Sweet! Good to be wrong sometimes :)

kantorn
Jan 19th, 2007, 04:14 AM
Excellent, just what I need.
But I do wonder: wouldn't an interface like ContextSourceAware be really nice? Today thera are no way of getting properties from a base LdapTemplate.

rasky
Jan 19th, 2007, 04:34 AM
Excellent, just what I need.
But I do wonder: wouldn't an interface like ContextSourceAware be really nice? Today thera are no way of getting properties from a base LdapTemplate.

I'm not sure if I understand what you would like to use the ContextSourceAware interface for. Could you elaborate?

davsclaus
Jan 20th, 2007, 06:42 AM
Even though Acegi is the security framework I would love Spring LDAP to have this simple login check in it's distribution.

Spring LDAP seems simple and easy to use for LDAP integration.

Well at least someone could create a JIRA for it and see if it gets votes.

ulsa
Jan 20th, 2007, 07:18 AM
There is now a JIRA issue LDAP-39 (http://opensource.atlassian.com/projects/spring/browse/LDAP-39) which proposes a simple authentication mechanism.

RaoulDuke
Feb 21st, 2007, 06:55 AM
Hi guys,

Just to write :THANK YOU !

After 2 weeks of seeking/testing/implementing a lot of "solutions" and ideas, I found this wonderful thread...

THANK YOU AGAIN !

You make me happy :)

nusa
Apr 23rd, 2007, 06:15 AM
Hi,

This is something that I need as well without using Acegi.
But, where is the getUserDn(login) method ?

Thanks.

mlarchet
Apr 26th, 2007, 05:11 AM
Here is the 'getUserDN' method, but it's my own implementation and maybe there's another way of doing it :


private String getUserDn(String login) {
log.debug("LdapServiceDao::getUserDn()");

// Context (usually injected by Spring)
String context = "ou=People";

// Building DN
DistinguishedName dn = new DistinguishedName(context);

// Building filter
EqualsFilter filter = new EqualsFilter("uid", login);

// Setting returning attributes, none in this case
String [] attrs = new String[0];
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
sc.setReturningAttributes(attrs);

// Searching for user
List<Name> results = ldapTemplate.search(dn, filter.encode(), sc, new DnContextMapper());

// Results
if(results.isEmpty() || results.size() != 1) {
log.error("LdapServiceDao::getUserDn() : User unknown " + login);
throw new LdapException("User unknown " + login);
}
return results.get(0).toString();
}

private class DnContextMapper implements ContextMapper {

public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
return context.getDn();
}
}


You need to customize it a bit (LDAPException for example) but the general idea is here.

davsclaus
May 8th, 2007, 08:58 AM
Just wanted to say thanks for the provided code here.

I do hope this great feature will be part of 1.2, sooner than later.

davsclaus
May 9th, 2007, 09:37 AM
Just wanted to inform that there was a problem with the code i copied from this forum.

You dont need to build a new DN object from what getUserDn() returns. Just pass the plain string to ctx.setUserName() instead of a DN object. That failed for me and Novell returned -32 (-601) errors that I took a long time to track down.


public boolean canLogin(String username, String password) {
String userDn;
try {
userDn = getUserDn(username);
} catch (UnknownUserException e) {
log.debug(e.getMessage());
return false;
}

log.debug(userDn);

LdapContextSource ctx = new LdapContextSource();
ctx.setUrl(url);
ctx.setUserName(userDn);
ctx.setPassword(password);
ctx.setCacheEnvironmentProperties(true);

try {
log.debug("Checking if user can login. password [" + password + "]");
ctx.afterPropertiesSet();
ctx.getReadWriteContext();

log.debug("User [" + username + "] can login");
return true;
}
catch (Exception e) {
log.debug("FAILED --- User [" + username + "] can not login");
log.error(e.getMessage());
return false;
}

}

private String getUserDn(String username) throws UnknownUserException {
log.debug("getUserDn() " + username);

String context = "o=HS";
DistinguishedName dn = new DistinguishedName(context);

// Building filter
EqualsFilter filter = new EqualsFilter("uid", username);

// Setting returning attributes, none in this case
String[] attrs = new String[0];
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
sc.setReturningAttributes(attrs);

// Searching for user
List results = template.search(dn, filter.encode(), sc, new DnContextMapper());

// Results
if (results.isEmpty() || results.size() != 1) {
// we expected excatly 1 element in the list
throw new UnknownUserException(username);
}

return results.get(0).toString();
}

private class DnContextMapper implements ContextMapper {

public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter) ctx;
return context.getDn();
}
}

justinkoke@gmail.com
May 9th, 2007, 10:15 PM
We have used similar code in our product to what is above, and came across one issue (at least when testing against AD, I haven't looked at other Directory Servers yet).

If you authenticate with a empty password (ie, your UI and business layers don't handle validation for a blank password) the ctx.getReadWriteContext(); still succeeds and you return true, however if you actually attempt to do a search with the context you create here you get the correct result.

Cheers,
Justin

ChuckAtEmbarq
Jul 12th, 2007, 08:12 PM
I've tried the code posted (below), and it works for me with one exception. When a blank password is provided, checkPassword() returns true. The workaround is easy of course, i.e. check for a blank password and fail. But, does anyone why it passes. (I admit that I don't know that much about ldap). Thanks




public boolean checkPassword(String login, String password) {
log.debug("LdapServiceDao::checkPassword()");

// Construction du DN
DistinguishedName dn = new DistinguishedName("ou=People,dc=univ,dc=fr");
dn.append(new DistinguishedName(getUserDn(login)));

// Connexion manuelle
LdapContextSource ctxSource = new LdapContextSource();
ctxSource.setUrl(url);
ctxSource.setUserName(dn.encode());
ctxSource.setPassword(password);
ctxSource.setPooled(false);
try {
ctxSource.afterPropertiesSet();
ctxSource.getReadWriteContext();
return true;
}
catch(Exception e) {
return false;
}
}

ChuckAtEmbarq
Jul 12th, 2007, 08:19 PM
The code below works for me with one exception. It returns true if the password is empty, i.e. "". The workaround is easy of course, e.g. return false immediately is an empty password is passed in. But, does anyone know why ldap doesn't throw an exception? Thanks




public boolean checkPassword(String login, String password) {
log.debug("LdapServiceDao::checkPassword()");

// Construction du DN
DistinguishedName dn = new DistinguishedName("ou=People,dc=univ,dc=fr");
dn.append(new DistinguishedName(getUserDn(login)));

// Connexion manuelle
LdapContextSource ctxSource = new LdapContextSource();
ctxSource.setUrl(url);
ctxSource.setUserName(dn.encode());
ctxSource.setPassword(password);
ctxSource.setPooled(false);
try {
ctxSource.afterPropertiesSet();
ctxSource.getReadWriteContext();
return true;
}
catch(Exception e) {
return false;
}
}

JEisen
Jul 23rd, 2007, 02:50 PM
I think it's because of both parameters aren't filled, LDAP defaults to anonymous bind. But I'm not positive.

miha
Aug 24th, 2007, 10:13 AM
I think it's because of both parameters aren't filled, LDAP defaults to anonymous bind. But I'm not positive.

This is, AFAIK, provider specific. When using Sun or Netscape or IBM directory servers you should get an AuthenticationException...Some other providers default to anonymous login if you don't provide the necessary credentials.
I don't know if this is configurable for different servers...

This is just my bit of insight....

Cheers!

sanjay_jadhav
Nov 12th, 2007, 08:34 AM
This is, AFAIK, provider specific. When using Sun or Netscape or IBM directory servers you should get an AuthenticationException...Some other providers default to anonymous login if you don't provide the necessary credentials.
I don't know if this is configurable for different servers...

This is just my bit of insight....

Cheers!

Using ldapTemplate for authentication would be better than hard coding dn or context. Looking for some solution from spring authors.

rasky
Nov 12th, 2007, 12:24 PM
Using ldapTemplate for authentication would be better than hard coding dn or context. Looking for some solution from spring authors.

That would be Acegi Security I guess...

dglassm
Nov 12th, 2007, 06:03 PM
Your ldap server allows anonymous bind ....

I've tried the code posted (below), and it works for me with one exception. When a blank password is provided, checkPassword() returns true. The workaround is easy of course, i.e. check for a blank password and fail. But, does anyone why it passes. (I admit that I don't know that much about ldap). Thanks




public boolean checkPassword(String login, String password) {
log.debug("LdapServiceDao::checkPassword()");

// Construction du DN
DistinguishedName dn = new DistinguishedName("ou=People,dc=univ,dc=fr");
dn.append(new DistinguishedName(getUserDn(login)));

// Connexion manuelle
LdapContextSource ctxSource = new LdapContextSource();
ctxSource.setUrl(url);
ctxSource.setUserName(dn.encode());
ctxSource.setPassword(password);
ctxSource.setPooled(false);
try {
ctxSource.afterPropertiesSet();
ctxSource.getReadWriteContext();
return true;
}
catch(Exception e) {
return false;
}
}

peralles
Jun 23rd, 2008, 02:49 PM
Hello guys, I'm trying to use the source code listed in this thread but a have one question... how should I create the Ldap Template to be used inside the getUserDn method??

mlarchet
Jun 24th, 2008, 02:10 AM
The checkPassword method is one of my DAO methods.
Others method are much more regular and use a private LdapTemplate, injected through Spring configuration.

Here is the full code of the LdapServiceDao class :

public class LdapServiceDao implements LdapService {

protected static Log log = LogFactory.getLog(LdapServiceDao.class);
private String base;
private String context;
private String url;
private String accountFilter;
private LdapTemplate ldapTemplate;

/**
* Positionne la base
* @param base
*/
public void setBase(String base) {
this.base = base;
}

/**
* Positionne le contexte
* @param context
*/
public void setContext(String context) {
this.context = context;
}

/**
* Positionne l'url
* @param url
*/
public void setUrl(String url) {
this.url = url;
}

/**
* Positionne le filtre pour la création de compte
* @param accountFilter
*/
public void setAccountFilter(String accountFilter) {
this.accountFilter = accountFilter;
}

/**
* Positionne le service LDAP
* @param ldapTemplate
*/
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}

/**
* Test étudiant
* @param login
* @return
*/
public boolean isStudent(String login) {
log.debug("LdapServiceDao::isStudent()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(context);
// Construction du filtre
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass","n2classetudiant"));
filter.and(new EqualsFilter("uid", login));
// Définition des attributs à retourner
String [] attrs = new String[0];
return !ldapTemplate.search(dn, filter.encode(), SearchControls.SUBTREE_SCOPE, attrs, new DnContextMapper()).isEmpty();
}

/**
* Test personnel
* @param login
* @return
*/
public boolean isEmployee(String login) {
log.debug("LdapServiceDao::isEmployee()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(context);
// Construction du filtre
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass","n2classpersonnel"));
filter.and(new EqualsFilter("uid", login));
// Définition des attributs à retourner
String [] attrs = new String[0];
return !ldapTemplate.search(dn, filter.encode(), SearchControls.SUBTREE_SCOPE, attrs, new DnContextMapper()).isEmpty();
}

/**
* Test ICN
* @param login
* @return
*/
public boolean isICN(String login) {
log.debug("LdapServiceDao::isICN()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(context);
// Construction du filtre
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass","n2classotherperson"));
filter.and(new EqualsFilter("n2typeobjet", "{otherpeople}CCM"));
filter.and(new EqualsFilter("uid", login));
// Définition des attributs à retourner
String [] attrs = new String[0];
return !ldapTemplate.search(dn, filter.encode(), SearchControls.SUBTREE_SCOPE, attrs, new DnContextMapper()).isEmpty();
}

/**
* Test création de compte
* @param login
* @return
*/
public boolean isAccountCreationAllowed(String login) {
log.debug("LdapServiceDao::isAccountCreationAllowed()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(context);
// Construction du filtre
String filter = accountFilter.replaceAll("\\[LOGIN\\]", login);
// Définition des attributs à retourner
String [] attrs = new String[0];
return !ldapTemplate.search(dn, filter, SearchControls.SUBTREE_SCOPE, attrs, new DnContextMapper()).isEmpty();
}

/**
* Vérifie un mot de passe
* @param login
* @param password
* @return
*/
public boolean checkPassword(String login, String password) {
log.debug("LdapServiceDao::checkPassword()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(base);
dn.append(new DistinguishedName(getUserDn(login)));
// Connexion manuelle
LdapContextSource ctxSource = new LdapContextSource();
ctxSource.setUrl(url);
ctxSource.setUserDn(dn.encode());
ctxSource.setPassword(password);
ctxSource.setPooled(false);
try {
ctxSource.afterPropertiesSet();
ctxSource.getReadWriteContext();
return true;
}
catch(Exception e) {
return false;
}
}

/**
* Ecrase un mot de passe
* @param login
* @param password
*/
public void setPassword(String login, String password) {
log.debug("LdapServiceDao::setPassword()");
try {
// Passage en SHA
String shaPassword = "{SHA}" + Encoder.encodeSHA(password);
// Calcul du DN
DistinguishedName dn = new DistinguishedName(getUserDn(login));
// Modification du mot de passe
ModificationItem [] mod = new ModificationItem[1];
mod[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userPassword", shaPassword));
ldapTemplate.modifyAttributes(dn, mod);
}
catch(DataAccessException e) {
log.error("LdapServiceDao::setPassword() :", e);
throw new LdapException("Erreur lors du changement de mot de passe", e);
}
}

/**
* Retourne un étudiant
* @param login
* @return
*/
public Student getStudent(String login) {
log.debug("LdapServiceDao::getStudent()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(getUserDn(login));
// Construction de la personne
String [] attributes = {"uid", "supannCodeINE", "n2datenaissance", "n2datevalidation"};
return (Student)ldapTemplate.lookup(dn, attributes, new AttributesStudentMapper());
}

/**
* Retourne le nom d'un utilisateur
* @param login
* @return
*/
public String getUsername(String login) {
log.debug("LdapServiceDao::getUsername()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(getUserDn(login));
// Récupération du nom
String [] attributes = {"displayName"};
return (String)ldapTemplate.lookup(dn, attributes, new UsernameMapper());
}

/**
* Retourne l'adresse électronique d'un utilisateur
* @param login
* @return
*/
public String getMail(String login) {
log.debug("LdapServiceDao::getMail()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(getUserDn(login));
// Récupration de l'adresse
String [] attributes = {"mail"};
return (String)ldapTemplate.lookup(dn, attributes, new MailMapper());
}

/**
* Retourne le DN d'un utilisateur
* @param login
* @return
*/
@SuppressWarnings("unchecked")
private String getUserDn(String login) {
log.debug("LdapServiceDao::getUserDn()");
// Construction du DN
DistinguishedName dn = new DistinguishedName(context);
// Construction du filtre
EqualsFilter filter = new EqualsFilter("uid", login);
// Définition des attributs à retourner
String [] attrs = new String[0];
// Recherche de l'utilisateur
List<Name> results = ldapTemplate.search(dn, filter.encode(), SearchControls.SUBTREE_SCOPE, attrs, new DnContextMapper());
// Résultat
if(results.isEmpty() || results.size() != 1) {
log.error("LdapServiceDao::getUserDn() : Utilisateur inconnu " + login);
throw new LdapException("Utilisateur inconnu " + login);
}
return results.get(0).toString();
}

private class DnContextMapper implements ContextMapper {
/**
* Mapping DN LDAP / UID
* @param ctx
* @return
*/
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
return context.getDn();
}
}

private class AttributesStudentMapper implements AttributesMapper {
/**
* Mapping Attributs LDAP / Objet Java
* @param attrs
* @return
*/
public Object mapFromAttributes(Attributes attrs) throws NamingException {
log.debug("LdapServiceDao_AttributesStudentMapper::mapFromAtt ributes()");
Student student = new Student();
// Récupration du login
student.setLogin(attrs.get("uid").get().toString());
// Récupration du code INE
student.setIne(attrs.get("supannCodeINE").get().toString());
// Récupération de la date de naissance
String date = attrs.get("n2datenaissance").get().toString();
BirthDate birthDate = new BirthDate();
birthDate.setYear(date.substring(0,4));
birthDate.setMonth(date.substring(4, 6));
birthDate.setDay(date.substring(6));
student.setBirthDate(birthDate);
// Validation de la charte graphique
student.setValidation(true);
if(attrs.get("n2datevalidation") == null) {
student.setValidation(false);
}
return student;
}
}

private class UsernameMapper implements AttributesMapper {
/**
* Mapping Attributs LDAP / Objet Java
* @param attrs
* @return
*/
public Object mapFromAttributes(Attributes attrs) throws NamingException {
log.debug("LdapServiceDao_UsernameMapper::mapFromAttributes()");
return attrs.get("displayName").get().toString();
}
}

private class MailMapper implements AttributesMapper {
/**
* Mapping Attributs LDAP / Objet Java
* @param attrs
* @return
*/
public Object mapFromAttributes(Attributes attrs) throws NamingException {
log.debug("LdapServiceDao_MailMapper::mapFromAttributes()");
return attrs.get("mail").get().toString();
}
}
}

mlarchet
Jun 24th, 2008, 02:10 AM
Here is the Spring configuration :

<!-- Connexion LDAP -->
<bean id="ldapSource" class="org.springframework.ldap.core.support.LdapContextS ource">
<property name="url" value="${ldap.url}/${ldap.base}" />
<property name="userName" value="${ldap.user}" />
<property name="password" value="${ldap.password}" />
</bean>

<!-- Service LDAP -->
<bean id="ldapService" class="fr.univnancy2.web.sesame.dao.ldap.LdapServiceDao">
<property name="base" value="${ldap.base}" />
<property name="context" value="${ldap.context}" />
<property name="url" value="${ldap.url}/${ldap.base}" />
<property name="accountFilter" value="${ldap.account.filter}" />
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>



${xxx} properties are defined with a PropertyPlaceHolderConfigurer.
Sorry but comments are in french...