Skip to content

Commit

Permalink
Merge pull request #51 from monitorjbl/ldap-integration
Browse files Browse the repository at this point in the history
Ldap integration for signin
  • Loading branch information
csokol committed Aug 26, 2014
2 parents 05b2bb1 + f376a12 commit 7feae13
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 47 deletions.
15 changes: 14 additions & 1 deletion pom.xml
Expand Up @@ -182,6 +182,19 @@
</dependency>
<!-- end of hibernate deps -->

<!-- LDAP support -->
<dependency>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-all</artifactId>
<version>1.0.0-M24</version>
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Embedded Solr -->
<dependency>
<groupId>org.apache.solr</groupId>
Expand Down Expand Up @@ -294,7 +307,7 @@
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/mamute/auth/AuthAPIException.java
@@ -0,0 +1,15 @@
package org.mamute.auth;


public class AuthAPIException extends RuntimeException {
private String authType;

public AuthAPIException(String authType, String msg, Throwable t) {
super(msg, t);
this.authType = authType;
}

public String getAuthType() {
return authType;
}
}
36 changes: 31 additions & 5 deletions src/main/java/org/mamute/auth/DefaultAuthenticator.java
Expand Up @@ -2,27 +2,53 @@

import javax.inject.Inject;

import br.com.caelum.vraptor.environment.Environment;
import org.mamute.dao.UserDAO;
import org.mamute.model.User;

public class DefaultAuthenticator {
public static final String AUTH_CONFIG = "auth.type";
public static final String LDAP_AUTH = "ldap";
public static final String DB_AUTH = "db";

@Inject private UserDAO users;
@Inject private Access system;
@Inject
private UserDAO users;
@Inject
private Access system;
@Inject
private LDAPApi ldap;
@Inject
private Environment env;

public boolean authenticate(String email, String password) {
User retrieved = users.findByMailAndPassword(email, password);
//auth credentials
if(!doAuthenticate(email, password)){
return false;
}

User retrieved = users.findByEmail(email);
if (retrieved == null) {
retrieved = users.findByMailAndLegacyPasswordAndUpdatePassword(email, password);
retrieved = users.findByMailAndLegacyPasswordAndUpdatePassword(email, password);
}
if (retrieved == null) {
return false;
}

system.login(retrieved);
return true;
}

private boolean doAuthenticate(String email, String password) {
String type = env.get(AUTH_CONFIG, DB_AUTH);
if (type.equals(LDAP_AUTH)) {
return ldap.authenticate(email, password);
} else if (type.equals(DB_AUTH)) {
return users.findByMailAndPassword(email, password) != null;
} else {
throw new RuntimeException("Unrecognized authentication type [" + type + "]");
}
}

public void signout() {
system.logout();
}
Expand Down
159 changes: 159 additions & 0 deletions src/main/java/org/mamute/auth/LDAPApi.java
@@ -0,0 +1,159 @@
package org.mamute.auth;

import br.com.caelum.vraptor.environment.Environment;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.mamute.dao.LoginMethodDAO;
import org.mamute.dao.UserDAO;
import org.mamute.model.LoginMethod;
import org.mamute.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import java.io.IOException;

import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
import static org.mamute.auth.DefaultAuthenticator.*;

public class LDAPApi {
private Logger logger = LoggerFactory.getLogger(LDAPApi.class);

public static final String LDAP_HOST = "ldap.host";
public static final String LDAP_PORT = "ldap.port";
public static final String LDAP_USER = "ldap.user";
public static final String LDAP_PASS = "ldap.pass";
public static final String LDAP_EMAIL = "ldap.emailAttr";
public static final String LDAP_NAME = "ldap.nameAttr";
public static final String LDAP_SURNAME = "ldap.surnameAttr";
public static final String LDAP_USER_DN = "ldap.userDn";
public static final String PLACHOLDER_PASSWORD = "ldap-password-ignore-me";

@Inject private Environment env;
@Inject private UserDAO users;
@Inject private LoginMethodDAO loginMethods;

private String host;
private Integer port;
private String user;
private String pass;
private String userDn;
private String emailAttr;
private String nameAttr;
private String surnameAttr;

/**
* Ensure that required variables are set if LDAP auth
* is enabled
*/
@PostConstruct
public void init() {
if (env.get(AUTH_CONFIG, "").equals(LDAP_AUTH)) {
host = assertValuePresent(LDAP_HOST);
port = Integer.parseInt(assertValuePresent(LDAP_PORT));
user = assertValuePresent(LDAP_USER);
pass = assertValuePresent(LDAP_PASS);
userDn = assertValuePresent(LDAP_USER_DN);

emailAttr = assertValuePresent(LDAP_EMAIL);
nameAttr = assertValuePresent(LDAP_NAME);
surnameAttr = env.get(LDAP_SURNAME, "");
}
}

/**
* Attempt to authenticate against LDAP directory. Accepts email addresses
* as well as plain usernames; emails will have the '@mail.com' portion
* stripped off before read.
*
* @param username
* @param password
* @return
*/
public boolean authenticate(String username, String password) {
try (LDAPResource ldap = new LDAPResource()) {
String formattedUser = username.replaceAll("@.+", "");
String cn = "cn=" + formattedUser + "," + userDn;

ldap.verifyCredentials(cn, password);
createUserIfNeeded(ldap, cn);

return true;
} catch (LdapAuthenticationException e) {
logger.debug("LDAP auth attempt failed");
return false;
} catch (LdapException | IOException e) {
logger.debug("LDAP connection error", e);
throw new AuthAPIException(LDAP_AUTH, "LDAP connection error", e);
}
}

private void createUserIfNeeded(LDAPResource ldap, String cn) throws LdapException {
Entry entry = ldap.getUser(cn);
String email = ldap.getAttribute(entry, emailAttr);
if (users.findByEmail(email) == null) {
String fullName = ldap.getAttribute(entry, nameAttr);
if (isNotEmpty(surnameAttr)) {
fullName += " " + ldap.getAttribute(entry, surnameAttr);
}

User user = new User(fullName.trim(), email);

LoginMethod brutalLogin = LoginMethod.brutalLogin(user, email, PLACHOLDER_PASSWORD);
user.add(brutalLogin);

users.save(user);
loginMethods.save(brutalLogin);
}
}

private String assertValuePresent(String field) {
String value = env.get(field, "");
if (isEmpty(value)) {
throw new RuntimeException("Field [" + field + "] is required when using LDAP authentication");
}
return value;
}

/**
* Acts as a session-level LDAP connection
*/
private class LDAPResource implements AutoCloseable {
LdapConnection connection;

private LDAPResource() throws LdapException {
connection = connection(user, pass);
}

private void verifyCredentials(String userCn, String password) throws LdapException, IOException {
try (LdapConnection conn = connection(userCn, password)) {
logger.debug("LDAP login from [" + userCn + "]");
}
}

private LdapConnection connection(String username, String password) throws LdapException {
LdapNetworkConnection conn = new LdapNetworkConnection(host, port);
conn.bind(username, password);
return conn;
}

private Entry getUser(String cn) throws LdapException {
return connection.lookup(cn);
}

private String getAttribute(Entry entry, String attribute) throws LdapException {
return entry.get(attribute).getString();
}

@Override
public void close() throws IOException {
connection.close();
}
}
}
48 changes: 28 additions & 20 deletions src/main/java/org/mamute/controllers/AuthController.java
Expand Up @@ -2,6 +2,8 @@

import javax.inject.Inject;

import br.com.caelum.vraptor.environment.Environment;
import org.mamute.auth.AuthAPIException;
import org.mamute.auth.DefaultAuthenticator;
import org.mamute.auth.FacebookAuthService;
import org.mamute.auth.GoogleAuthService;
Expand All @@ -19,14 +21,14 @@
@Routed
@Controller
public class AuthController extends BaseController {

@Inject private DefaultAuthenticator auth;
@Inject private FacebookAuthService facebook;
@Inject private GoogleAuthService google;
@Inject private Result result;
@Inject private UrlValidator urlValidator;
@Inject private LoginValidator validator;
@Inject private DefaultAuthenticator auth;
@Inject private FacebookAuthService facebook;
@Inject private GoogleAuthService google;
@Inject private Result result;
@Inject private Environment env;
@Inject private UrlValidator urlValidator;
@Inject private LoginValidator validator;

@Get
public void loginForm(String redirectUrl) {
String facebookUrl = facebook.getOauthUrl(redirectUrl);
Expand All @@ -37,34 +39,40 @@ public void loginForm(String redirectUrl) {
result.include("facebookUrl", facebookUrl);
result.include("googleUrl", googleUrl);
}

@Post
public void login(String email, String password, String redirectUrl) {
if (validator.validate(email, password) && auth.authenticate(email, password)) {
redirectToRightUrl(redirectUrl);
} else {
includeAsList("mamuteMessages", i18n("error", "auth.invalid.login"));
try {
if (validator.validate(email, password) && auth.authenticate(email, password)) {
redirectToRightUrl(redirectUrl);
} else {
includeAsList("mamuteMessages", i18n("error", "auth.invalid.login"));
redirectTo(this).loginForm(redirectUrl);
validator.onErrorRedirectTo(this).loginForm(redirectUrl);
}
} catch (AuthAPIException e) {
includeAsList("mamuteMessages", i18n("error", "auth.configuration.error", e.getAuthType()));
redirectTo(this).loginForm(redirectUrl);
validator.onErrorRedirectTo(this).loginForm(redirectUrl);
}
}

@CustomBrutauthRules(LoggedRule.class)
@Get
public void logout() {
auth.signout();
redirectTo(ListController.class).home(null);
}

private void redirectToRightUrl(String redirectUrl) {
boolean valid = urlValidator.isValid(redirectUrl);
if (!valid) {
includeAsList("mamuteMessages", i18n("error", "error.invalid.url", redirectUrl));
}
if (redirectUrl != null && !redirectUrl.isEmpty() && valid) {
redirectTo(redirectUrl);
} else {
redirectTo(ListController.class).home(null);
}
if (redirectUrl != null && !redirectUrl.isEmpty() && valid) {
redirectTo(redirectUrl);
} else {
redirectTo(ListController.class).home(null);
}
}
}

0 comments on commit 7feae13

Please sign in to comment.