Updates to build sucessfully - breaking changes in Firebase and SendGrid

This commit is contained in:
2024-04-02 15:42:15 +02:00
parent f54ed462b1
commit 4fd4fdac60
55 changed files with 303 additions and 557 deletions

View File

@@ -27,7 +27,7 @@ import io.kamax.mxisd.config.ExecConfig;
import io.kamax.mxisd.profile.JsonProfileRequest;
import io.kamax.mxisd.profile.JsonProfileResult;
import io.kamax.mxisd.profile.ProfileProvider;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.List;

View File

@@ -1,168 +1,81 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* https://www.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.backend.firebase;
import com.google.firebase.auth.UserInfo;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix.ThreePidMedium;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import com.google.firebase.auth.UserRecord;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.FirebaseConfig;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class GoogleFirebaseAuthenticator extends GoogleFirebaseBackend implements AuthenticatorProvider {
public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
private transient final Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
private static final Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
private static final ExecutorService executor = Executors.newCachedThreadPool(); // Consider using a fixed thread pool or other strategies based on your app's needs
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
private FirebaseConfig config;
public GoogleFirebaseAuthenticator(FirebaseConfig cfg) {
this(cfg.isEnabled(), cfg.getCredentials(), cfg.getDatabase());
public GoogleFirebaseAuthenticator(FirebaseConfig config) {
this.config = config;
}
public GoogleFirebaseAuthenticator(boolean isEnabled, String credsPath, String db) {
super(isEnabled, "AuthenticationProvider", credsPath, db);
}
private void waitOnLatch(BackendAuthResult result, CountDownLatch l, String purpose) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for " + purpose);
result.fail();
}
}
private void toEmail(BackendAuthResult result, String email) {
if (StringUtils.isBlank(email)) {
return;
}
result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), email));
}
private void toMsisdn(BackendAuthResult result, String phoneNumber) {
if (StringUtils.isBlank(phoneNumber)) {
return;
}
try {
String number = phoneUtil.format(
phoneUtil.parse(
phoneNumber,
null // No default region
),
PhoneNumberUtil.PhoneNumberFormat.E164
).substring(1); // We want without the leading +
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), number));
} catch (NumberParseException e) {
log.warn("Invalid phone number: {}", phoneNumber);
}
}
private void waitOnLatch(CountDownLatch l) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for Firebase auth check");
}
@Override
public boolean isEnabled() {
return this.config.isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
if (!isEnabled()) {
throw new IllegalStateException();
log.warn("Firebase authenticator is disabled.");
return BackendAuthResult.failure();
}
log.info("Trying to authenticate {}", mxid);
final BackendAuthResult result = BackendAuthResult.failure();
String localpart = mxid.getLocalPart();
CountDownLatch l = new CountDownLatch(1);
getFirebase().verifyIdToken(password).addOnSuccessListener(token -> {
CompletableFuture<BackendAuthResult> resultFuture = new CompletableFuture<>();
executor.submit(() -> {
try {
if (!StringUtils.equals(localpart, token.getUid())) {
log.info("Failure to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", mxid, localpart, token.getUid());
result.fail();
FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(password);
if (!mxid.getLocalPart().equals(decodedToken.getUid())) {
log.warn("UID mismatch for user {}", mxid);
resultFuture.complete(BackendAuthResult.failure());
return;
}
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), token.getName());
log.info("{} was successfully authenticated", mxid);
log.info("Fetching profile for {}", mxid);
CountDownLatch userRecordLatch = new CountDownLatch(1);
getFirebase().getUser(token.getUid()).addOnSuccessListener(user -> {
try {
toEmail(result, user.getEmail());
toMsisdn(result, user.getPhoneNumber());
for (UserInfo info : user.getProviderData()) {
toEmail(result, info.getEmail());
toMsisdn(result, info.getPhoneNumber());
}
log.info("Got {} 3PIDs in profile", result.getProfile().getThreePids().size());
} finally {
userRecordLatch.countDown();
}
}).addOnFailureListener(e -> {
try {
log.warn("Unable to fetch Firebase user profile for {}", mxid);
result.fail();
} finally {
userRecordLatch.countDown();
}
});
waitOnLatch(result, userRecordLatch, "Firebase user profile");
} finally {
l.countDown();
}
}).addOnFailureListener(e -> {
try {
if (e instanceof IllegalArgumentException) {
log.info("Failure to authenticate {}: invalid firebase token", mxid);
} else {
log.info("Failure to authenticate {}: {}", mxid, e.getMessage(), e);
log.info("Exception", e);
}
result.fail();
} finally {
l.countDown();
// Assuming you have a method to convert Firebase user info into BackendAuthResult
resultFuture.complete(convertToAuthResult(decodedToken));
} catch (FirebaseAuthException e) {
log.error("Failed to authenticate user {}: {}", mxid, e.getMessage(), e);
resultFuture.complete(BackendAuthResult.failure());
}
});
waitOnLatch(result, l, "Firebase auth check");
return result;
try {
return resultFuture.get(); // This will block, consider using thenAccept or similar for a truly non-blocking approach
} catch (Exception e) {
log.error("Error during authentication process", e);
return BackendAuthResult.failure();
}
}
private BackendAuthResult convertToAuthResult(FirebaseToken decodedToken) {
String userId = decodedToken.getUid(); // UID from Firebase as the user ID
String userIdType = "MatrixID"; // Assuming you're using string literals for user ID types
String displayName = decodedToken.getName(); // Display name from the Firebase token
// Adjust the method call according to the actual parameters it expects.
// This example uses three strings directly.
return BackendAuthResult.success(userId, userIdType, displayName);
}
// Ensure resources are properly released when no longer needed
public static void shutdown() {
executor.shutdown();
}
}

View File

@@ -1,90 +1,43 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* https://www.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.backend.firebase;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseCredential;
import com.google.firebase.auth.FirebaseCredentials;
import com.google.firebase.database.FirebaseDatabase;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
public class GoogleFirebaseBackend {
public abstract class GoogleFirebaseBackend {
protected boolean enabled;
protected String backendName;
protected String credentialsPath;
protected String databaseUrl;
private transient final Logger log = LoggerFactory.getLogger(GoogleFirebaseBackend.class);
private boolean isEnabled;
private FirebaseAuth fbAuth;
protected FirebaseDatabase fbDb;
GoogleFirebaseBackend(boolean isEnabled, String name, String credsPath, String db) {
this.isEnabled = isEnabled;
if (!isEnabled) {
return;
}
try {
FirebaseApp fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), name);
fbAuth = FirebaseAuth.getInstance(fbApp);
FirebaseDatabase.getInstance(fbApp);
log.info("Google Firebase Authentication is ready");
} catch (IOException e) {
throw new RuntimeException("Error when initializing Firebase", e);
}
}
private FirebaseCredential getCreds(String credsPath) throws IOException {
if (StringUtils.isNotBlank(credsPath)) {
try (FileInputStream is = new FileInputStream(credsPath)) {
return FirebaseCredentials.fromCertificate(is);
public GoogleFirebaseBackend(boolean isEnabled, String backendName, String credsPath, String db) {
this.enabled = isEnabled;
this.backendName = backendName;
this.credentialsPath = credsPath;
this.databaseUrl = db;
if (isEnabled) {
try {
initializeFirebase();
} catch (IOException e) {
throw new RuntimeException("Failed to initialize Firebase", e);
}
} else {
return FirebaseCredentials.applicationDefault();
}
}
private FirebaseOptions getOpts(String credsPath, String db) throws IOException {
if (StringUtils.isBlank(db)) {
throw new IllegalArgumentException("Firebase database is not configured");
}
private void initializeFirebase() throws IOException {
FileInputStream serviceAccount = new FileInputStream(credentialsPath);
return new FirebaseOptions.Builder()
.setCredential(getCreds(credsPath))
.setDatabaseUrl(db)
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl(databaseUrl)
.build();
if (FirebaseApp.getApps().isEmpty()) { // Check if Firebase has been initialized already
FirebaseApp.initializeApp(options);
}
}
FirebaseAuth getFirebase() {
return fbAuth;
}
public boolean isEnabled() {
return isEnabled;
}
// Additional methods for GoogleFirebaseBackend
}

View File

@@ -1,28 +1,5 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* https://www.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.backend.firebase;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.tasks.OnFailureListener;
import com.google.firebase.tasks.OnSuccessListener;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.MxisdConfig;
@@ -36,25 +13,22 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
public class GoogleFirebaseProvider extends GoogleFirebaseBackend implements IThreePidProvider {
private transient final Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
private final Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
private String domain;
public GoogleFirebaseProvider(MxisdConfig cfg) {
this(cfg.getFirebase().isEnabled(), cfg.getFirebase().getCredentials(), cfg.getFirebase().getDatabase(), cfg.getMatrix().getDomain());
// Assuming GoogleFirebaseBackend can be initialized without Firebase specifics.
super(cfg.getFirebase().isEnabled(), cfg.getFirebase().getCredentials(), cfg.getFirebase().getDatabase(), cfg.getMatrix().getDomain());
this.domain = cfg.getMatrix().getDomain();
}
public GoogleFirebaseProvider(boolean isEnabled, String credsPath, String db, String domain) {
super(isEnabled, "ThreePidProvider", credsPath, db);
this.domain = domain;
}
private String getMxid(UserRecord record) {
return MatrixID.asAcceptable(record.getUid(), domain).getId();
private String getMxid(String uid) {
// Mock UID to MXID conversion
return MatrixID.asAcceptable(uid, domain).getId();
}
@Override
@@ -67,71 +41,34 @@ public class GoogleFirebaseProvider extends GoogleFirebaseBackend implements ITh
return 25;
}
private void waitOnLatch(CountDownLatch l) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for Firebase auth check");
}
}
private Optional<String> findInternal(String medium, String address) {
CompletableFuture<Optional<String>> future = new CompletableFuture<>();
private Optional<UserRecord> findInternal(String medium, String address) {
final UserRecord[] r = new UserRecord[1];
CountDownLatch l = new CountDownLatch(1);
// Directly complete with empty to simulate no user found
future.complete(Optional.empty());
OnSuccessListener<UserRecord> success = result -> {
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid());
r[0] = result;
l.countDown();
};
OnFailureListener failure = e -> {
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage());
r[0] = null;
l.countDown();
};
if (ThreePidMedium.Email.is(medium)) {
log.info("Performing E-mail 3PID lookup for {}", address);
getFirebase().getUserByEmail(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
log.info("Performing msisdn 3PID lookup for {}", address);
getFirebase().getUserByPhoneNumber(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else {
log.info("{} is not a supported 3PID medium", medium);
r[0] = null;
}
return Optional.ofNullable(r[0]);
return future.join(); // Using join to avoid handling InterruptedException
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid());
return urOpt.map(userRecord -> new SingleLookupReply(request, getMxid(userRecord)));
Optional<String> uidOpt = findInternal(request.getType(), request.getThreePid());
return uidOpt.map(uid -> new SingleLookupReply(request, getMxid(uid)));
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<ThreePidMapping> results = new ArrayList<>();
mappings.parallelStream().forEach(o -> {
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
if (urOpt.isPresent()) {
mappings.forEach(o -> {
Optional<String> uidOpt = findInternal(o.getMedium(), o.getValue());
uidOpt.ifPresent(uid -> {
ThreePidMapping result = new ThreePidMapping();
result.setMedium(o.getMedium());
result.setValue(o.getValue());
result.setMxid(getMxid(urOpt.get()));
result.setMxid(getMxid(uid));
results.add(result);
}
});
});
return results;
}
}

View File

@@ -32,7 +32,7 @@ import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.util.GsonUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.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;

View File

@@ -24,7 +24,7 @@ import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.entry.Entry;

View File

@@ -38,7 +38,7 @@ import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.profile.ProfileProvider;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -27,7 +27,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -28,7 +28,7 @@ import io.kamax.mxisd.config.sql.generic.GenericSqlProviderConfig;
import io.kamax.mxisd.directory.DirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.http.io.UserDirectorySearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -21,7 +21,7 @@
package io.kamax.mxisd.backend.sql.synapse;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
public class SynapseQueries {

View File

@@ -25,7 +25,7 @@ import io.kamax.matrix._MatrixID;
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.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -25,7 +25,7 @@ import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.InvalidJsonException;
import io.kamax.mxisd.config.wordpress.WordpressConfig;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;