diff --git a/application.example.yaml b/application.example.yaml
index 36908d4..0d6307c 100644
--- a/application.example.yaml
+++ b/application.example.yaml
@@ -97,41 +97,83 @@ lookup:
ldap:
+
+ # Global enable/disable switch
enabled: true
- tls: false
- host: 'localhost'
- port: 389
- bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
- bindPassword: 'password'
- baseDn: 'CN=Users,DC=example,DC=org'
- # How should we resolve the Matrix ID in case of a match using the attribute.
+ # Connection configuration to the LDAP server
+ connection:
+
+ # If the connection should be secure
+ tls: false
+
+ # Host to connect to
+ host: 'localhost'
+
+ # Port to connect to
+ port: 389
+
+ # Bind DN to use when performing lookups
+ bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
+
+ # Bind password to use
+ bindPassword: 'password'
+
+ # Base DN used in all queries
+ baseDn: 'CN=Users,DC=example,DC=org'
+
+ # How to map Matrix attributes with LDAP attributes when performing lookup/auth
+ attributes:
+
+ # The username/login that will be looked up or used to build Matrix IDs
+ uid:
+
+ # How should we resolve the Matrix ID in case of a match using the attribute.
+ #
+ # The following type are supported:
+ # - uid : the attribute only contains the UID part of the Matrix ID. e.g. 'john.doe' in @john.doe:example.org
+ # - mxid : the attribute contains the full Matrix ID - e.g. '@john.doe:example.org'
+ type: 'uid'
+
+ # The attribute containing the binding itself. This value will be used differently depending on the type.
+ #
+ # /!\ This should match the synapse LDAP Authenticator 'uid' configuration /!\
+ #
+ # Typical values:
+ # - For type 'uid': 'userPrincipalName' or 'uid' or 'saMAccountName'
+ # - For type 'mxid', regardless of the directory type, we recommend using 'pager' as it is a standard attribute and
+ # is typically not used.
+ value: 'userPrincipalName'
+
+ # The display name of the user
+ name: 'displayName'
+
+ # Configuration section relating the authentication of users performed via LDAP.
#
- # The following type are supported:
- # - uid : the attribute only contains the UID part of the Matrix ID. e.g. 'john.doe' in @john.doe:example.org
- # - mxid : the attribute contains the full Matrix ID - e.g. '@john.doe:example.org'
- type: 'uid'
+ # This can be done using the REST Auth module for synapse and pointing it to the identity server.
+ # See https://github.com/maxidor/matrix-synapse-rest-auth
+ auth:
- # The attribute containing the binding itself. This value will be used differently depending on the type.
- #
- # /!\ This should match the synapse LDAP Authenticator 'uid' configuration /!\
- #
- # Typical values:
- # - For type 'uid': 'userPrincipalName' or 'uid' or 'saMAccountName'
- # - For type 'mxid', regardless of the directory type, we recommend using 'pager' as it is a standard attribute and
- # is typically not used.
- attribute: 'userPrincipalName'
-
- # Configure each 3PID type with a dedicated query.
- mappings:
- email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))"
-
- # Phone numbers query.
+ # What to filter potential users by, typically by using a dedicated group.
+ # If this value is not set, login check will be performed for all entities within the LDAP
#
- # Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN
- # This format does not include international prefix (+ or 00) and therefore has to be put in the query.
- # Adapt this to your needs for each attribute.
- msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
+ # Example: (memberOf=CN=Matrix Users,CN=Users,DC=example,DC=org)
+ filter: ''
+
+ # Configuration section relating to identity lookups
+ identity:
+
+ # Configure each 3PID type with a dedicated query.
+ medium:
+ # E-mail query
+ email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))"
+
+ # Phone numbers query
+ #
+ # Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN
+ # This format does not include international prefix (+ or 00) and therefore has to be put in the query.
+ # Adapt this to your needs for each attribute.
+ msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
diff --git a/src/main/groovy/io/kamax/mxisd/config/LdapConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/LdapConfig.groovy
deleted file mode 100644
index e90046e..0000000
--- a/src/main/groovy/io/kamax/mxisd/config/LdapConfig.groovy
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * mxisd - Matrix Identity Server Daemon
- * Copyright (C) 2017 Maxime Dor
- *
- * https://max.kamax.io/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package io.kamax.mxisd.config
-
-import org.apache.commons.lang.StringUtils
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.InitializingBean
-import org.springframework.boot.context.properties.ConfigurationProperties
-import org.springframework.context.annotation.Configuration
-
-@Configuration
-@ConfigurationProperties(prefix = "ldap")
-class LdapConfig implements InitializingBean {
-
- private Logger log = LoggerFactory.getLogger(LdapConfig.class)
-
- private boolean enabled
- private boolean tls
- private String host
- private int port
- private String baseDn
- private String type
- private String attribute
- private String bindDn
- private String bindPassword
- private Map mappings
-
- boolean getEnabled() {
- return enabled
- }
-
- void setEnabled(boolean enabled) {
- this.enabled = enabled
- }
-
- boolean getTls() {
- return tls
- }
-
- void setTls(boolean tls) {
- this.tls = tls
- }
-
- String getHost() {
- return host
- }
-
- void setHost(String host) {
- this.host = host
- }
-
- int getPort() {
- return port
- }
-
- void setPort(int port) {
- this.port = port
- }
-
- String getBaseDn() {
- return baseDn
- }
-
- void setBaseDn(String baseDn) {
- this.baseDn = baseDn
- }
-
- String getType() {
- return type
- }
-
- void setType(String type) {
- this.type = type
- }
-
- String getAttribute() {
- return attribute
- }
-
- void setAttribute(String attribute) {
- this.attribute = attribute
- }
-
- String getBindDn() {
- return bindDn
- }
-
- void setBindDn(String bindDn) {
- this.bindDn = bindDn
- }
-
- String getBindPassword() {
- return bindPassword
- }
-
- void setBindPassword(String bindPassword) {
- this.bindPassword = bindPassword
- }
-
- Map getMappings() {
- return mappings
- }
-
- void setMappings(Map mappings) {
- this.mappings = mappings
- }
-
- Optional getMapping(String type) {
- if (mappings == null) {
- return Optional.empty()
- }
-
- return Optional.ofNullable(mappings.get(type))
- }
-
- @Override
- void afterPropertiesSet() throws Exception {
- log.info("LDAP enabled: {}", getEnabled())
-
- if (!getEnabled()) {
- return
- }
-
- log.info("Matrix ID type: {}", getType())
- log.info("LDAP Host: {}", getHost())
- log.info("LDAP Bind DN: {}", getBindDn())
- log.info("LDAP Attribute: {}", getAttribute())
-
- if (StringUtils.isBlank(getHost())) {
- throw new IllegalStateException("LDAP Host must be configured!")
- }
- if (StringUtils.isBlank(getAttribute())) {
- throw new IllegalStateException("LDAP attribute must be configured!")
- }
- }
-
-}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java
new file mode 100644
index 0000000..f8139ee
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java
@@ -0,0 +1,29 @@
+package io.kamax.mxisd.config.ldap;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap.attribute")
+public class LdapAttributeConfig {
+
+ private LdapAttributeUidConfig uid;
+ private String name;
+
+ public LdapAttributeUidConfig getUid() {
+ return uid;
+ }
+
+ public void setUid(LdapAttributeUidConfig uid) {
+ this.uid = uid;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java
new file mode 100644
index 0000000..771a630
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java
@@ -0,0 +1,29 @@
+package io.kamax.mxisd.config.ldap;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap.attribute.uid")
+public class LdapAttributeUidConfig {
+
+ private String type;
+ private String value;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java
new file mode 100644
index 0000000..011500e
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java
@@ -0,0 +1,20 @@
+package io.kamax.mxisd.config.ldap;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap.auth")
+public class LdapAuthConfig {
+
+ private String filter;
+
+ public String getFilter() {
+ return filter;
+ }
+
+ public void setFilter(String filter) {
+ this.filter = filter;
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy
new file mode 100644
index 0000000..55fd3d7
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy
@@ -0,0 +1,125 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2017 Maxime Dor
+ *
+ * https://max.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.config.ldap
+
+import groovy.json.JsonOutput
+import org.apache.commons.lang.StringUtils
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.context.annotation.Configuration
+
+import javax.annotation.PostConstruct
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap")
+class LdapConfig {
+
+ private Logger log = LoggerFactory.getLogger(LdapConfig.class)
+
+ private boolean enabled
+
+ @Autowired
+ private LdapConnectionConfig conn
+ private LdapAttributeConfig attribute
+ private LdapAuthConfig auth
+ private LdapIdentityConfig identity
+
+ boolean isEnabled() {
+ return enabled
+ }
+
+ void setEnabled(boolean enabled) {
+ this.enabled = enabled
+ }
+
+ LdapConnectionConfig getConn() {
+ return conn
+ }
+
+ void setConn(LdapConnectionConfig conn) {
+ this.conn = conn
+ }
+
+ LdapAttributeConfig getAttribute() {
+ return attribute
+ }
+
+ void setAttribute(LdapAttributeConfig attribute) {
+ this.attribute = attribute
+ }
+
+ LdapAuthConfig getAuth() {
+ return auth
+ }
+
+ void setAuth(LdapAuthConfig auth) {
+ this.auth = auth
+ }
+
+ LdapIdentityConfig getIdentity() {
+ return identity
+ }
+
+ void setIdentity(LdapIdentityConfig identity) {
+ this.identity = identity
+ }
+
+ @PostConstruct
+ void afterPropertiesSet() {
+ log.info("--- LDAP Config ---")
+ log.info("Enabled: {}", isEnabled())
+
+ if (!isEnabled()) {
+ return
+ }
+
+ if (StringUtils.isBlank(conn.getHost())) {
+ throw new IllegalStateException("LDAP Host must be configured!")
+ }
+
+ if (1 > conn.getPort() || 65535 < conn.getPort()) {
+ throw new IllegalStateException("LDAP port is not valid")
+ }
+
+ if (StringUtils.isBlank(attribute.getUid().getType())) {
+ throw new IllegalStateException("Attribute UID Type cannot be empty")
+ }
+
+
+ if (StringUtils.isBlank(attribute.getUid().getValue())) {
+ throw new IllegalStateException("Attribute UID value cannot be empty")
+ }
+
+
+ log.info("Conn: {}", JsonOutput.toJson(conn))
+ log.info("Host: {}", conn.getHost())
+ log.info("Port: {}", conn.getPort())
+ log.info("Bind DN: {}", conn.getBindDn())
+ log.info("Base DN: {}", conn.getBaseDn())
+
+ log.info("Attribute: {}", JsonOutput.toJson(attribute))
+ log.info("Auth: {}", JsonOutput.toJson(auth))
+ log.info("Identity: {}", JsonOutput.toJson(identity))
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java
new file mode 100644
index 0000000..7f85e5d
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java
@@ -0,0 +1,65 @@
+package io.kamax.mxisd.config.ldap;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap.connection")
+public class LdapConnectionConfig {
+
+ private boolean tls;
+ private String host;
+ private int port;
+ private String bindDn;
+ private String bindPassword;
+ private String baseDn;
+
+ public boolean isTls() {
+ return tls;
+ }
+
+ public void setTls(boolean tls) {
+ this.tls = tls;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getBindDn() {
+ return bindDn;
+ }
+
+ public void setBindDn(String bindDn) {
+ this.bindDn = bindDn;
+ }
+
+ public String getBindPassword() {
+ return bindPassword;
+ }
+
+ public void setBindPassword(String bindPassword) {
+ this.bindPassword = bindPassword;
+ }
+
+ public String getBaseDn() {
+ return baseDn;
+ }
+
+ public void setBaseDn(String baseDn) {
+ this.baseDn = baseDn;
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java
new file mode 100644
index 0000000..c6839e4
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java
@@ -0,0 +1,28 @@
+package io.kamax.mxisd.config.ldap;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Configuration
+@ConfigurationProperties(prefix = "ldap.identity")
+public class LdapIdentityConfig {
+
+ private Map medium = new HashMap<>();
+
+ public Map getMedium() {
+ return medium;
+ }
+
+ public Optional getQuery(String key) {
+ return Optional.ofNullable(medium.get(key));
+ }
+
+ public void setMedium(Map medium) {
+ this.medium = medium;
+ }
+
+}
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 281fb82..392e5ff 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,10 @@
package io.kamax.mxisd.lookup.provider
-import io.kamax.mxisd.config.LdapConfig
+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
import io.kamax.mxisd.lookup.ThreePidMapping
import org.apache.commons.lang.StringUtils
@@ -38,7 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component
-class LdapProvider implements IThreePidProvider {
+class LdapProvider implements IThreePidProvider, AuthenticatorProvider {
public static final String UID = "uid"
public static final String MATRIX_ID = "mxid"
@@ -53,7 +55,28 @@ class LdapProvider implements IThreePidProvider {
@Override
boolean isEnabled() {
- return ldapCfg.getEnabled()
+ return ldapCfg.isEnabled()
+ }
+
+ private LdapConnection getConn() {
+ return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls())
+ }
+
+ private void bind(LdapConnection conn) {
+ 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()
+ }
}
@Override
@@ -67,20 +90,22 @@ class LdapProvider implements IThreePidProvider {
}
Optional lookup(LdapConnection conn, String medium, String value) {
- Optional queryOpt = ldapCfg.getMapping(medium)
+ String uidAttribute = ldapCfg.getAttribute().getUid().getValue()
+
+ Optional queryOpt = ldapCfg.getIdentity().getQuery(medium)
if (!queryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
return Optional.empty()
}
String searchQuery = queryOpt.get().replaceAll("%3pid", value)
- EntryCursor cursor = conn.search(ldapCfg.getBaseDn(), searchQuery, SearchScope.SUBTREE, ldapCfg.getAttribute())
+ EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
try {
while (cursor.next()) {
Entry entry = cursor.get()
log.info("Found possible match, DN: {}", entry.getDn().getName())
- Attribute attribute = entry.get(ldapCfg.getAttribute())
+ Attribute attribute = entry.get(uidAttribute)
if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute())
continue
@@ -94,12 +119,13 @@ class LdapProvider implements IThreePidProvider {
StringBuilder matrixId = new StringBuilder()
// TODO Should we turn this block into a map of functions?
- if (StringUtils.equals(UID, ldapCfg.getType())) {
+ String uidType = ldapCfg.getAttribute().getUid().getType()
+ if (StringUtils.equals(UID, uidType)) {
matrixId.append("@").append(data).append(":").append(srvCfg.getName())
- } else if (StringUtils.equals(MATRIX_ID, ldapCfg.getType())) {
+ } else if (StringUtils.equals(MATRIX_ID, uidType)) {
matrixId.append(data)
} else {
- log.warn("Bind was found but type {} is not supported", ldapCfg.getType())
+ log.warn("Bind was found but type {} is not supported", uidType)
continue
}
@@ -119,9 +145,9 @@ class LdapProvider implements IThreePidProvider {
Optional> find(SingleLookupRequest request) {
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
- LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort(), ldapCfg.getTls())
+ LdapConnection conn = getConn()
try {
- conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword())
+ bind(conn)
Optional mxid = lookup(conn, request.getType(), request.getThreePid())
if (mxid.isPresent()) {
@@ -147,9 +173,9 @@ class LdapProvider implements IThreePidProvider {
log.info("Looking up {} mappings", mappings.size())
List mappingsFound = new ArrayList<>()
- LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort())
+ LdapConnection conn = getConn()
try {
- conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword())
+ bind(conn)
for (ThreePidMapping mapping : mappings) {
try {