Skip to content

Commit

Permalink
login: Support simplified LDAP login
Browse files Browse the repository at this point in the history
  • Loading branch information
doortts committed Mar 13, 2017
1 parent b8e243a commit 1b8f271
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 11 deletions.
69 changes: 61 additions & 8 deletions app/controllers/UserApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import models.*;
import models.enumeration.Operation;
import models.enumeration.UserState;
import models.support.LdapUser;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.RandomNumberGenerator;
Expand All @@ -39,6 +40,10 @@
import utils.*;
import views.html.user.*;

import javax.annotation.Nonnull;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.NamingException;
import java.util.*;

import static com.feth.play.module.mail.Mailer.getEmailName;
Expand Down Expand Up @@ -207,7 +212,12 @@ private static Result loginByFormRequest() {
return redirect(getLoginFormURLWithRedirectURL());
}

User authenticate = authenticateWithPlainPassword(sourceUser.loginId, authInfoForm.get().password);
User authenticate = User.anonymous;
if (LdapService.useLdap) {
authenticate = authenticateWithLdap(authInfoForm.get().loginIdOrEmail, authInfoForm.get().password);
} else {
authenticate = authenticateWithPlainPassword(sourceUser.loginId, authInfoForm.get().password);
}

if (!authenticate.isAnonymous()) {
addUserInfoToSession(authenticate);
Expand Down Expand Up @@ -275,7 +285,12 @@ private static Result loginByAjaxRequest() {
return notFound(getObjectNodeWithMessage("user.deleted"));
}

User user = authenticateWithPlainPassword(sourceUser.loginId, authInfoForm.get().password);
User user = User.anonymous;
if (LdapService.useLdap) {
user = authenticateWithLdap(authInfoForm.get().loginIdOrEmail, authInfoForm.get().password);
} else {
user = authenticateWithPlainPassword(sourceUser.loginId, authInfoForm.get().password);
}

if (!user.isAnonymous()) {
if (authInfoForm.get().rememberMe) {
Expand Down Expand Up @@ -378,7 +393,7 @@ public static User createLocalUserWithOAuth(UserCredential userCredential){
forceOAuthLogout();
return User.anonymous;
}
User created = createUserDelegate(userCredential);
User created = createUserDelegate(userCredential.name, userCredential.email, null);

if (created.state == UserState.LOCKED) {
flash(Constants.INFO, "user.signup.requested");
Expand All @@ -398,15 +413,19 @@ private static void forceOAuthLogout() {
session().put("pa.url.orig", routes.Application.oAuthLogout().url());
}

private static User createUserDelegate(UserCredential userCredential) {
String loginIdCandidate = userCredential.email.substring(0, userCredential.email.indexOf("@"));
private static User createUserDelegate(@Nonnull String name, @Nonnull String email, String password) {
String loginIdCandidate = email.substring(0, email.indexOf("@"));

User user = new User();
user.loginId = generateLoginId(user, loginIdCandidate);
user.name = userCredential.name;
user.email = userCredential.email;
user.name = name;
user.email = email;

user.password = (new SecureRandomNumberGenerator()).nextBytes().toBase64(); // random password because created with OAuth
if(StringUtils.isEmpty(password)){
user.password = (new SecureRandomNumberGenerator()).nextBytes().toBase64(); // random password because created with OAuth
} else {
user.password = password;
}

return createNewUser(user);
}
Expand Down Expand Up @@ -1014,6 +1033,40 @@ private static User authenticate(String loginId, String password, boolean hashed
return User.anonymous;
}

public static User authenticateWithLdap(String loginIdOrEmail, String password) {
LdapService ldapService = new LdapService();
try {
LdapUser ldapUser = ldapService.authenticate(loginIdOrEmail, password);
User localUserFoundByLdapLogin = User.findByEmail(ldapUser.getEmail());
if (localUserFoundByLdapLogin.isAnonymous()) {
User created = createUserDelegate(ldapUser.getDisplayName(), ldapUser.getEmail(), password);
if (created.state == UserState.LOCKED) {
flash(Constants.INFO, "user.signup.requested");
return User.anonymous;
}
return created;
} else {
if(!localUserFoundByLdapLogin.isSamePassword(password)) {
User.resetPassword(localUserFoundByLdapLogin.loginId, password);
}
return localUserFoundByLdapLogin;
}
} catch (CommunicationException e) {
play.Logger.error("Cannot connect to ldap server \n" + e.getMessage());
e.printStackTrace();
return User.anonymous;
}
catch (AuthenticationException e) {
flash(Constants.WARNING, Messages.get("user.login.invalid"));
play.Logger.warn("login failed \n" + e.getMessage());
return User.anonymous;
} catch (NamingException e) {
play.Logger.error("Cannot connect to ldap server \n" + e.getMessage());
e.printStackTrace();
return User.anonymous;
}
}

public static boolean isUseSignUpConfirm(){
Configuration config = play.Play.application().configuration();
Boolean useSignUpConfirm = config.getBoolean("signup.require.admin.confirm");
Expand Down
12 changes: 10 additions & 2 deletions app/models/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -464,12 +464,20 @@ public boolean isAnonymous() {
*/
public static void resetPassword(String loginId, String newPassword) {
User user = findByLoginId(loginId);
user.password = new Sha256Hash(newPassword, ByteSource.Util.bytes(user.passwordSalt), 1024)
.toBase64();
user.password = getHashedStringForPassword(newPassword, user.passwordSalt);
CacheStore.yonaUsers.put(user.id, user);
user.save();
}

public boolean isSamePassword(String newPassword) {
return this.password.equals(getHashedStringForPassword(newPassword, this.passwordSalt));
}

private static String getHashedStringForPassword(String newPassword, String salt) {
return new Sha256Hash(newPassword, ByteSource.Util.bytes(salt), 1024)
.toBase64();
}

@Override
public Resource asResource() {
return new GlobalResource() {
Expand Down
63 changes: 63 additions & 0 deletions app/models/support/LdapUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Yona, 21st Century Project Hosting SW
* <p>
* Copyright Yona & Yobi Authors & NAVER Corp.
* https://yona.io
**/
package models.support;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;

public class LdapUser {
private Attribute displayName;
private Attribute email;
private Attribute userLoginId;
private Attribute department;

public LdapUser(Attribute displayName, Attribute email, Attribute userLoginId, Attribute department) {
this.displayName = displayName;
this.email = email;
this.userLoginId = userLoginId;
this.department = department;
}

public String getDisplayName() {
return getString(this.displayName);
}

private String getString(Attribute attr) {
try {
if (attr.get() == null){
return "";
} else {
return attr.get().toString();
}
} catch (NamingException e) {
e.printStackTrace();
return "";
}
}

public String getEmail() {
return getString(email);
}

public String getUserLoginId() {
return getString(userLoginId);
}

public String getDepartment() {
return getString(department);
}

@Override
public String toString() {
return "LdapUser{" +
"displayName='" + getDisplayName() + '\'' +
", email='" + getEmail() + '\'' +
", userId='" + getUserLoginId() + '\'' +
", department='" + getDepartment() + '\'' +
'}';
}
}
6 changes: 5 additions & 1 deletion app/utils/BasicAuthAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ public User authenticate(Request request) throws UnsupportedEncodingException, M
User authUser = parseCredentials(credential);

if (authUser != null) {
return UserApp.authenticateWithPlainPassword(authUser.loginId, authUser.password);
if (LdapService.useLdap) {
return UserApp.authenticateWithLdap(authUser.loginId, authUser.password);
} else {
return UserApp.authenticateWithPlainPassword(authUser.loginId, authUser.password);
}
} else {
return User.anonymous;
}
Expand Down
93 changes: 93 additions & 0 deletions app/utils/LdapService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Yona, 21st Century Project Hosting SW
* <p>
* Copyright Yona & Yobi Authors & NAVER Corp.
* https://yona.io
**/
package utils;

import models.support.LdapUser;
import play.Play;

import javax.annotation.Nonnull;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.Hashtable;

public class LdapService {
public static final boolean useLdap = Play.application().configuration().getBoolean("application.use.ldap.login.supoort",false);
private static final String HOST = Play.application().configuration().getString("ldap.host", "127.0.0.1");
private static final String PORT = Play.application().configuration().getString("ldap.port", "389");
private static final String BASE_DN = Play.application().configuration().getString("ldap.baseDN", "");
private static final String DN_POSTFIX = Play.application().configuration().getString("ldap.distinguishedNamePostfix", "");
private static final String PROTOCOL = Play.application().configuration().getString("protocol", "ldap");
private static final int TIMEOUT = 5000; //ms

public LdapUser authenticate(String username, String password) throws NamingException {

Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put("com.sun.jndi.ldap.connect.timeout", ""+(TIMEOUT));
env.put(Context.PROVIDER_URL, PROTOCOL + "://" + HOST + ":" + PORT);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, getProperUsernameGuessing(username));
env.put(Context.SECURITY_CREDENTIALS, password);

DirContext authContext = new InitialDirContext(env);
SearchResult searchResult = findUser(authContext, username, searchFilter(username));
if (searchResult != null) {
return getLdapUser(searchResult);
} else {
return null;
}
}

private LdapUser getLdapUser(SearchResult searchResult) throws NamingException {
Attributes attr = searchResult.getAttributes();
return new LdapUser(attr.get("displayName"),
attr.get("mail"),
attr.get("sAMAccountName"),
attr.get("department"));
}

private String searchFilter(@Nonnull String username) {
if(username.contains("@")){
return "mail";
} else {
return "sAMAccountName";
}
}

private String getProperUsernameGuessing(@Nonnull String username){
if(username.contains("@")){
return username;
} else {
return "CN=" + username + ", " + DN_POSTFIX;
}
}

private SearchResult findUser(DirContext ctx, String username, String filter) throws NamingException {

String searchFilter = "(&(objectClass=user)(" + filter + "=" + username + "))";

SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);

NamingEnumeration<SearchResult> results = ctx.search(BASE_DN, searchFilter, searchControls);

SearchResult searchResult = null;
if(results.hasMoreElements()) {
searchResult = (SearchResult) results.nextElement();

//make sure there is not another item available, there should be only 1 match
if(results.hasMoreElements()) {
System.err.println("Matched multiple users for the username: " + username);
return null;
}
}

return searchResult;
}
}
15 changes: 15 additions & 0 deletions conf/application.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,21 @@ application.use.social.login.name.sync = false
# choice: github, google
application.social.login.support = "github, google"

# LDAP Login Support
# ~~~~~~~~~~~~~~~~~
#
application.use.ldap.login.supoort = false
ldap {
host = "ldap.forumsys.com"
# default: ldap.port=389, ldaps.port=636
port = 389
# protocol: ldap or ldaps. If you want to use SSL/TLS, use 'ldaps'
protocol = "ldap"
baseDN = "ou=scientists,dc=example,dc=com"
# If your ldap service's distinguishedName is 'CN=username,OU=user,DC=abc,DC=com', postfix is 'OU=xxx,DC=abc,DC=com'
distinguishedNamePostfix = "OU=user,DC=abc,DC=com"
}

# If you enable to use social login, set followings
play-easymail {
from {
Expand Down

0 comments on commit 1b8f271

Please sign in to comment.