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;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserIdType;
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.lookup.ThreePidMapping;
import org.slf4j.Logger;
@@ -40,6 +44,9 @@ public class AuthManager {
@Autowired
private List<AuthenticatorProvider> providers = new ArrayList<>();
@Autowired
private MatrixConfig mxCfg;
@Autowired
private InvitationManager invMgr;
@@ -49,17 +56,32 @@ public class AuthManager {
continue;
}
UserAuthResult result = provider.authenticate(id, password);
BackendAuthResult result = provider.authenticate(id, password);
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());
for (ThreePid pid : result.getThreePids()) {
for (ThreePid pid : authResult.getThreePids()) {
log.info("Processing {} for {}", pid, id);
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid()));
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, authResult.getMxid()));
}
invMgr.lookupMappingsForInvites();
return result;
return authResult;
}
}

View File

@@ -20,12 +20,10 @@
package io.kamax.mxisd.auth.provider;
import io.kamax.mxisd.auth.UserAuthResult;
public interface AuthenticatorProvider {
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/>.
*/
package io.kamax.mxisd.backend.rest;
package io.kamax.mxisd.auth.provider;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import java.util.ArrayList;
import java.util.List;
public class RestAuthReplyJson {
public class BackendAuthResult {
public static class RestAuthProfileData {
public static class BackendAuthProfile {
private String displayName;
private List<ThreePid> threePids = new ArrayList<>();
@@ -36,46 +38,51 @@ public class RestAuthReplyJson {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<ThreePid> getThreePids() {
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 String mxid;
private RestAuthProfileData profile;
private UserID id;
private BackendAuthProfile profile = new BackendAuthProfile();
public boolean isSuccess() {
public Boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
public UserID getId() {
return id;
}
public String getMxid() {
return mxid;
}
public void setMxid(String mxid) {
this.mxid = mxid;
}
public RestAuthProfileData getProfile() {
public BackendAuthProfile getProfile() {
return profile;
}
public void setProfile(RestAuthProfileData profile) {
this.profile = profile;
public BackendAuthResult withThreePid(ThreePid threePid) {
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.OnSuccessListener
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.BackendAuthResult
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -49,7 +51,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
private FirebaseApp fbApp;
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 {
l.await(timeout, unit);
} catch (InterruptedException e) {
@@ -108,22 +110,21 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
}
@Override
public UserAuthResult authenticate(String id, String password) {
public BackendAuthResult authenticate(String id, String password) {
if (!isEnabled()) {
throw new IllegalStateException();
}
final UserAuthResult result = new UserAuthResult();
log.info("Trying to authenticate {}", id);
Matcher m = matrixIdLaxPattern.matcher(id);
if (!m.matches()) {
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);
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
@Override
@@ -131,13 +132,12 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
try {
if (!StringUtils.equals(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;
}
result = BackendAuthResult.success(id, UserIdType.MatrixID, token.getName());
log.info("{} was successfully authenticated", id);
result.success(id, token.getName());
log.info("Fetching profile for {}", id);
CountDownLatch userRecordLatch = new CountDownLatch(1);
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
@@ -145,12 +145,13 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
void onSuccess(UserRecord user) {
try {
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())) {
result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber());
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber()));
}
} finally {
userRecordLatch.countDown();
}
@@ -160,7 +161,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
void onFailure(@NonNull Exception e) {
try {
log.warn("Unable to fetch Firebase user profile for {}", id);
result.failure();
result = BackendAuthResult.failure();
} finally {
userRecordLatch.countDown();
}
@@ -183,7 +184,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
log.info("Exception", e);
}
result.failure();
result = BackendAuthResult.failure();
} finally {
l.countDown()
}

View File

@@ -21,8 +21,9 @@
package io.kamax.mxisd.backend.ldap;
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.BackendAuthResult;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
@@ -53,7 +54,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
}
@Override
public UserAuthResult authenticate(String id, String password) {
public BackendAuthResult authenticate(String id, String password) {
log.info("Performing auth for {}", id);
LdapConnection conn = getConn();
@@ -88,7 +89,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
conn.bind(entry.getDn(), password);
} catch (LdapException e) {
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());
@@ -97,7 +98,8 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
log.info("Authentication successful for {}", entry.getDn().getName());
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) {
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);
return new UserAuthResult().failure();
return BackendAuthResult.failure();
} catch (LdapException | IOException | CursorException e) {
throw new RuntimeException(e);
} finally {

View File

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

View File

@@ -20,8 +20,8 @@
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.BackendAuthResult;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig;
import io.kamax.mxisd.invitation.InvitationManager;
@@ -50,11 +50,11 @@ public class SqlAuthProvider implements AuthenticatorProvider {
}
@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");
invMgr.lookupMappingsForInvites();
return new UserAuthResult().failure();
return BackendAuthResult.failure();
}
}