From 4b0d549dd67ea2aea645ab3f245aa25921b7a3e2 Mon Sep 17 00:00:00 2001 From: Maxime Dor Date: Tue, 5 Sep 2017 21:31:36 +0200 Subject: [PATCH] Add LDAP Auth support with synapse REST Auth module --- application.example.yaml | 2 + build.gradle | 2 +- .../io/kamax/mxisd/auth/UserAuthResult.java | 4 +- .../mxisd/auth/provider/LdapAuthProvider.java | 115 ++++++++++++++++++ .../config/ldap/LdapAttributeUidConfig.java | 11 ++ .../mxisd/lookup/provider/LdapProvider.groovy | 19 +-- 6 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java diff --git a/application.example.yaml b/application.example.yaml index 0d6307c..c4ad5dc 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -158,6 +158,8 @@ ldap: # If this value is not set, login check will be performed for all entities within the LDAP # # Example: (memberOf=CN=Matrix Users,CN=Users,DC=example,DC=org) + # + # /!\ Currently NOT supported due to a possible bug in LDAP library /!\ filter: '' # Configuration section relating to identity lookups diff --git a/build.gradle b/build.gradle index d54ab8f..f506d2b 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ dependencies { compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE' // Matrix Java SDK - compile 'io.kamax:matrix-java-sdk:0.0.1' + compile 'io.kamax:matrix-java-sdk:0.0.2' // ed25519 handling compile 'net.i2p.crypto:eddsa:0.1.0' diff --git a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java index 61100e4..16e1cf6 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java +++ b/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java @@ -14,10 +14,12 @@ public class UserAuthResult { return this; } - public void success(String mxid, String displayName) { + public UserAuthResult success(String mxid, String displayName) { setSuccess(true); setMxid(mxid); setDisplayName(displayName); + + return this; } public boolean isSuccess() { diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java b/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java new file mode 100644 index 0000000..ff2ddc4 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/LdapAuthProvider.java @@ -0,0 +1,115 @@ +package io.kamax.mxisd.auth.provider; + +import io.kamax.matrix.MatrixID; +import io.kamax.mxisd.auth.UserAuthResult; +import io.kamax.mxisd.config.ldap.LdapConfig; +import io.kamax.mxisd.lookup.provider.LdapProvider; +import org.apache.commons.lang.StringUtils; +import org.apache.directory.api.ldap.model.cursor.CursorException; +import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.entry.Attribute; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.ldap.client.api.LdapNetworkConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class LdapAuthProvider implements AuthenticatorProvider { + + private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class); + + @Autowired + private LdapConfig ldapCfg; + + private LdapConnection getConn() { + return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls()); + } + + private void bind(LdapConnection conn) throws LdapException { + conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword()); + } + + private String getUidAttribute() { + return ldapCfg.getAttribute().getUid().getValue(); + } + + @Override + public boolean isEnabled() { + return ldapCfg.isEnabled(); + } + + @Override + public UserAuthResult authenticate(String id, String password) { + log.info("Performing auth for {}", id); + + LdapConnection conn = getConn(); + try { + bind(conn); + + String uidType = ldapCfg.getAttribute().getUid().getType(); + MatrixID mxIdExt = new MatrixID(id); + String userFilterValue = StringUtils.equals(LdapProvider.UID, uidType) ? mxIdExt.getLocalPart() : mxIdExt.getId(); + String userFilter = "(" + ldapCfg.getAttribute().getUid().getValue() + "=" + userFilterValue + ")"; + EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), ldapCfg.getAttribute().getName()); + try { + while (cursor.next()) { + Entry entry = cursor.get(); + String dn = entry.getDn().getName(); + log.info("Checking possible match, DN: {}", dn); + + Attribute attribute = entry.get(getUidAttribute()); + if (attribute == null) { + log.info("DN {}: no attribute {}, skpping", dn, getUidAttribute()); + continue; + } + + String data = attribute.get().toString(); + if (data.length() < 1) { + log.info("DN {}: empty attribute {}, skipping", getUidAttribute()); + continue; + } + + log.info("Attempting authentication on LDAP for {}", dn); + try { + conn.bind(entry.getDn(), password); + } catch (LdapException e) { + log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage()); + return new UserAuthResult().failure(); + } + + Attribute nameAttribute = entry.get(ldapCfg.getAttribute().getName()); + String name = nameAttribute != null ? nameAttribute.get().toString() : null; + + log.info("Authentication successful for {}", entry.getDn().getName()); + log.info("DN {} is a valid match", dn); + + return new UserAuthResult().success(mxIdExt.getId(), name); + } + } catch (CursorLdapReferralException e) { + log.warn("Entity for {} is only available via referral, skipping", mxIdExt); + } finally { + cursor.close(); + } + + log.info("No match were found for {}", id); + return new UserAuthResult().failure(); + } catch (LdapException | IOException | CursorException e) { + throw new RuntimeException(e); + } finally { + try { + conn.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java index 771a630..a56044a 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java +++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java @@ -1,8 +1,12 @@ package io.kamax.mxisd.config.ldap; +import io.kamax.mxisd.lookup.provider.LdapProvider; +import org.apache.commons.lang.StringUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import javax.annotation.PostConstruct; + @Configuration @ConfigurationProperties(prefix = "ldap.attribute.uid") public class LdapAttributeUidConfig { @@ -26,4 +30,11 @@ public class LdapAttributeUidConfig { this.value = value; } + @PostConstruct + public void postConstruct() { + if (!StringUtils.equals(LdapProvider.UID, getType()) && !StringUtils.equals(LdapProvider.MATRIX_ID, getType())) { + throw new IllegalArgumentException("Unsupported LDAP UID type: " + getType()); + } + } + } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy index 392e5ff..1984f32 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/LdapProvider.groovy @@ -20,8 +20,6 @@ package io.kamax.mxisd.lookup.provider -import io.kamax.mxisd.auth.UserAuthResult -import io.kamax.mxisd.auth.provider.AuthenticatorProvider import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.config.ldap.LdapConfig import io.kamax.mxisd.lookup.SingleLookupRequest @@ -40,7 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @Component -class LdapProvider implements IThreePidProvider, AuthenticatorProvider { +class LdapProvider implements IThreePidProvider { public static final String UID = "uid" public static final String MATRIX_ID = "mxid" @@ -66,17 +64,8 @@ class LdapProvider implements IThreePidProvider, AuthenticatorProvider { conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword()) } - @Override - UserAuthResult authenticate(String id, String password) { - LdapConnection conn = getConn() - try { - bind(conn) - - // TODO finish this - return new UserAuthResult().failure() - } finally { - conn.close() - } + private String getUidAttribute() { + return ldapCfg.getAttribute().getUid().getValue(); } @Override @@ -90,7 +79,7 @@ class LdapProvider implements IThreePidProvider, AuthenticatorProvider { } Optional lookup(LdapConnection conn, String medium, String value) { - String uidAttribute = ldapCfg.getAttribute().getUid().getValue() + String uidAttribute = getUidAttribute() Optional queryOpt = ldapCfg.getIdentity().getQuery(medium) if (!queryOpt.isPresent()) {