Streamline Backend auth mechanism/return values

This commit is contained in:
Maxime Dor
2017-09-17 21:19:29 +02:00
parent 0182ec7251
commit efc54e73f2
9 changed files with 182 additions and 66 deletions

View File

@@ -0,0 +1,46 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd;
// FIXME consider integrating in matrix-java-sdk?
public class UserID {
private String type;
private String value;
protected UserID() {
// stub for (de)serialization
}
public UserID(String type, String value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd;
import org.apache.commons.lang.StringUtils;
// FIXME consider integrating in matrix-java-sdk?
public enum UserIdType {
Localpart("localpart"),
MatrixID("mxid"),
EmailLocalpart("email_localpart"),
Email("email");
private String id;
UserIdType(String id) {
this.id = id;
}
public String getId() {
return id;
}
public boolean is(String id) {
return StringUtils.equalsIgnoreCase(this.id, id);
}
}

View File

@@ -20,8 +20,12 @@
package io.kamax.mxisd.auth; package io.kamax.mxisd.auth;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -40,6 +44,9 @@ public class AuthManager {
@Autowired @Autowired
private List<AuthenticatorProvider> providers = new ArrayList<>(); private List<AuthenticatorProvider> providers = new ArrayList<>();
@Autowired
private MatrixConfig mxCfg;
@Autowired @Autowired
private InvitationManager invMgr; private InvitationManager invMgr;
@@ -49,17 +56,32 @@ public class AuthManager {
continue; continue;
} }
UserAuthResult result = provider.authenticate(id, password); BackendAuthResult result = provider.authenticate(id, password);
if (result.isSuccess()) { if (result.isSuccess()) {
String mxId;
if (UserIdType.Localpart.is(result.getId().getType())) {
mxId = new MatrixID(result.getId().getValue(), mxCfg.getDomain()).getId();
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
mxId = new MatrixID(result.getId().getValue()).getId();
} else {
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
continue;
}
UserAuthResult authResult = new UserAuthResult().success(mxId, result.getProfile().getDisplayName());
for (ThreePid pid : result.getProfile().getThreePids()) {
authResult.withThreePid(pid.getMedium(), pid.getAddress());
}
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName()); log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
for (ThreePid pid : result.getThreePids()) { for (ThreePid pid : authResult.getThreePids()) {
log.info("Processing {} for {}", pid, id); log.info("Processing {} for {}", pid, id);
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid())); invMgr.publishMappingIfInvited(new ThreePidMapping(pid, authResult.getMxid()));
} }
invMgr.lookupMappingsForInvites(); invMgr.lookupMappingsForInvites();
return result; return authResult;
} }
} }

View File

@@ -20,12 +20,10 @@
package io.kamax.mxisd.auth.provider; package io.kamax.mxisd.auth.provider;
import io.kamax.mxisd.auth.UserAuthResult;
public interface AuthenticatorProvider { public interface AuthenticatorProvider {
boolean isEnabled(); boolean isEnabled();
UserAuthResult authenticate(String id, String password); BackendAuthResult authenticate(String id, String password);
} }

View File

@@ -18,16 +18,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.backend.rest; package io.kamax.mxisd.auth.provider;
import io.kamax.mxisd.ThreePid; import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class RestAuthReplyJson { public class BackendAuthResult {
public static class RestAuthProfileData { public static class BackendAuthProfile {
private String displayName; private String displayName;
private List<ThreePid> threePids = new ArrayList<>(); private List<ThreePid> threePids = new ArrayList<>();
@@ -36,46 +38,51 @@ public class RestAuthReplyJson {
return displayName; return displayName;
} }
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<ThreePid> getThreePids() { public List<ThreePid> getThreePids() {
return threePids; return threePids;
} }
public void setThreePids(List<ThreePid> threePids) {
this.threePids = threePids;
} }
public static BackendAuthResult failure() {
BackendAuthResult r = new BackendAuthResult();
r.success = false;
return r;
}
public static BackendAuthResult success(String id, UserIdType type, String displayName) {
return success(id, type.getId(), displayName);
}
public static BackendAuthResult success(String id, String type, String displayName) {
BackendAuthResult r = new BackendAuthResult();
r.success = true;
r.id = new UserID(type, id);
r.profile = new BackendAuthProfile();
r.profile.displayName = displayName;
return r;
} }
private Boolean success; private Boolean success;
private String mxid; private UserID id;
private RestAuthProfileData profile; private BackendAuthProfile profile = new BackendAuthProfile();
public boolean isSuccess() { public Boolean isSuccess() {
return success; return success;
} }
public void setSuccess(boolean success) { public UserID getId() {
this.success = success; return id;
} }
public String getMxid() { public BackendAuthProfile getProfile() {
return mxid;
}
public void setMxid(String mxid) {
this.mxid = mxid;
}
public RestAuthProfileData getProfile() {
return profile; return profile;
} }
public void setProfile(RestAuthProfileData profile) { public BackendAuthResult withThreePid(ThreePid threePid) {
this.profile = profile; this.profile.threePids.add(threePid);
return this;
} }
} }

View File

@@ -27,8 +27,10 @@ import com.google.firebase.internal.NonNull
import com.google.firebase.tasks.OnFailureListener import com.google.firebase.tasks.OnFailureListener
import com.google.firebase.tasks.OnSuccessListener import com.google.firebase.tasks.OnSuccessListener
import io.kamax.matrix.ThreePidMedium import io.kamax.matrix.ThreePidMedium
import io.kamax.mxisd.auth.UserAuthResult import io.kamax.mxisd.ThreePid
import io.kamax.mxisd.UserIdType
import io.kamax.mxisd.auth.provider.AuthenticatorProvider import io.kamax.mxisd.auth.provider.AuthenticatorProvider
import io.kamax.mxisd.auth.provider.BackendAuthResult
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -49,7 +51,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
private FirebaseApp fbApp; private FirebaseApp fbApp;
private FirebaseAuth fbAuth; private FirebaseAuth fbAuth;
private void waitOnLatch(UserAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) { private void waitOnLatch(BackendAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
try { try {
l.await(timeout, unit); l.await(timeout, unit);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -108,22 +110,21 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
} }
@Override @Override
public UserAuthResult authenticate(String id, String password) { public BackendAuthResult authenticate(String id, String password) {
if (!isEnabled()) { if (!isEnabled()) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
final UserAuthResult result = new UserAuthResult();
log.info("Trying to authenticate {}", id); log.info("Trying to authenticate {}", id);
Matcher m = matrixIdLaxPattern.matcher(id); Matcher m = matrixIdLaxPattern.matcher(id);
if (!m.matches()) { if (!m.matches()) {
log.warn("Could not validate {} as a Matrix ID", id); log.warn("Could not validate {} as a Matrix ID", id);
result.failure(); BackendAuthResult.failure();
} }
String localpart = m.group(1); BackendAuthResult result = BackendAuthResult.failure();
String localpart = m.group(1);
CountDownLatch l = new CountDownLatch(1); CountDownLatch l = new CountDownLatch(1);
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() { fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
@Override @Override
@@ -131,13 +132,12 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
try { try {
if (!StringUtils.equals(localpart, token.getUid())) { if (!StringUtils.equals(localpart, token.getUid())) {
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid()); log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
result.failure(); result = BackendAuthResult.failure();
return; return;
} }
result = BackendAuthResult.success(id, UserIdType.MatrixID, token.getName());
log.info("{} was successfully authenticated", id); log.info("{} was successfully authenticated", id);
result.success(id, token.getName());
log.info("Fetching profile for {}", id); log.info("Fetching profile for {}", id);
CountDownLatch userRecordLatch = new CountDownLatch(1); CountDownLatch userRecordLatch = new CountDownLatch(1);
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() { fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
@@ -145,12 +145,13 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
void onSuccess(UserRecord user) { void onSuccess(UserRecord user) {
try { try {
if (StringUtils.isNotBlank(user.getEmail())) { if (StringUtils.isNotBlank(user.getEmail())) {
result.withThreePid(ThreePidMedium.Email, user.getEmail()); result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), user.getEmail()));
} }
if (StringUtils.isNotBlank(user.getPhoneNumber())) { if (StringUtils.isNotBlank(user.getPhoneNumber())) {
result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber()); result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber()));
} }
} finally { } finally {
userRecordLatch.countDown(); userRecordLatch.countDown();
} }
@@ -160,7 +161,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
void onFailure(@NonNull Exception e) { void onFailure(@NonNull Exception e) {
try { try {
log.warn("Unable to fetch Firebase user profile for {}", id); log.warn("Unable to fetch Firebase user profile for {}", id);
result.failure(); result = BackendAuthResult.failure();
} finally { } finally {
userRecordLatch.countDown(); userRecordLatch.countDown();
} }
@@ -183,7 +184,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
log.info("Exception", e); log.info("Exception", e);
} }
result.failure(); result = BackendAuthResult.failure();
} finally { } finally {
l.countDown() l.countDown()
} }

View File

@@ -21,8 +21,9 @@
package io.kamax.mxisd.backend.ldap; package io.kamax.mxisd.backend.ldap;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.auth.UserAuthResult; import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException; 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.CursorLdapReferralException;
@@ -53,7 +54,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
} }
@Override @Override
public UserAuthResult authenticate(String id, String password) { public BackendAuthResult authenticate(String id, String password) {
log.info("Performing auth for {}", id); log.info("Performing auth for {}", id);
LdapConnection conn = getConn(); LdapConnection conn = getConn();
@@ -88,7 +89,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
conn.bind(entry.getDn(), password); conn.bind(entry.getDn(), password);
} catch (LdapException e) { } catch (LdapException e) {
log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage()); log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage());
return new UserAuthResult().failure(); return BackendAuthResult.failure();
} }
Attribute nameAttribute = entry.get(getCfg().getAttribute().getName()); Attribute nameAttribute = entry.get(getCfg().getAttribute().getName());
@@ -97,7 +98,8 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
log.info("Authentication successful for {}", entry.getDn().getName()); log.info("Authentication successful for {}", entry.getDn().getName());
log.info("DN {} is a valid match", dn); log.info("DN {} is a valid match", dn);
return new UserAuthResult().success(mxIdExt.getId(), name); // TODO should we canonicalize the MXID?
return BackendAuthResult.success(mxIdExt.getId(), UserIdType.MatrixID, name);
} }
} catch (CursorLdapReferralException e) { } catch (CursorLdapReferralException e) {
log.warn("Entity for {} is only available via referral, skipping", mxIdExt); log.warn("Entity for {} is only available via referral, skipping", mxIdExt);
@@ -106,7 +108,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
} }
log.info("No match were found for {}", id); log.info("No match were found for {}", id);
return new UserAuthResult().failure(); return BackendAuthResult.failure();
} catch (LdapException | IOException | CursorException e) { } catch (LdapException | IOException | CursorException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {

View File

@@ -25,8 +25,8 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID; import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.auth.UserAuthResult;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.rest.RestBackendConfig; import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.util.GsonParser; import io.kamax.mxisd.util.GsonParser;
import io.kamax.mxisd.util.RestClientUtils; import io.kamax.mxisd.util.RestClientUtils;
@@ -62,7 +62,7 @@ public class RestAuthProvider implements AuthenticatorProvider {
} }
@Override @Override
public UserAuthResult authenticate(String id, String password) { public BackendAuthResult authenticate(String id, String password) {
_MatrixID mxid = new MatrixID(id); _MatrixID mxid = new MatrixID(id);
RestAuthRequestJson auth = new RestAuthRequestJson(); RestAuthRequestJson auth = new RestAuthRequestJson();
auth.setMxid(id); auth.setMxid(id);
@@ -72,19 +72,12 @@ public class RestAuthProvider implements AuthenticatorProvider {
HttpUriRequest req = RestClientUtils.post(cfg.getEndpoints().getAuth(), gson, "auth", auth); HttpUriRequest req = RestClientUtils.post(cfg.getEndpoints().getAuth(), gson, "auth", auth);
try (CloseableHttpResponse res = client.execute(req)) { try (CloseableHttpResponse res = client.execute(req)) {
UserAuthResult result = new UserAuthResult();
int status = res.getStatusLine().getStatusCode(); int status = res.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) { if (status < 200 || status >= 300) {
return result.failure(); return BackendAuthResult.failure();
} }
RestAuthReplyJson reply = parser.parse(res, "auth", RestAuthReplyJson.class); return parser.parse(res, "auth", BackendAuthResult.class);
if (!reply.isSuccess()) {
return result.failure();
}
return result.success(reply.getMxid(), reply.getProfile().getDisplayName());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -20,8 +20,8 @@
package io.kamax.mxisd.backend.sql; package io.kamax.mxisd.backend.sql;
import io.kamax.mxisd.auth.UserAuthResult;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig; import io.kamax.mxisd.config.sql.SqlProviderConfig;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;
@@ -50,11 +50,11 @@ public class SqlAuthProvider implements AuthenticatorProvider {
} }
@Override @Override
public UserAuthResult authenticate(String id, String password) { public BackendAuthResult authenticate(String id, String password) {
log.info("Performing dummy authentication try to force invite mapping refresh"); log.info("Performing dummy authentication try to force invite mapping refresh");
invMgr.lookupMappingsForInvites(); invMgr.lookupMappingsForInvites();
return new UserAuthResult().failure(); return BackendAuthResult.failure();
} }
} }