Files
mxids/src/main/groovy/io/kamax/mxisd/lookup/provider/GoogleFirebaseProvider.groovy
2017-09-13 01:16:02 +02:00

191 lines
6.4 KiB
Groovy

/*
* 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.lookup.provider
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.auth.UserRecord
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.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
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.function.Consumer
import java.util.regex.Pattern
public class GoogleFirebaseProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)");
private boolean isEnabled;
private String domain;
private FirebaseApp fbApp;
private FirebaseAuth fbAuth;
public GoogleFirebaseProvider(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public GoogleFirebaseProvider(String credsPath, String db, String domain) {
this(true);
this.domain = domain;
try {
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider");
fbAuth = FirebaseAuth.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)) {
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
} 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");
}
return new FirebaseOptions.Builder()
.setCredential(getCreds(credsPath))
.setDatabaseUrl(db)
.build();
}
private String getMxid(UserRecord record) {
return "@${record.getUid()}:${domain}";
}
@Override
public boolean isEnabled() {
return isEnabled;
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
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<UserRecord> findInternal(String medium, String address) {
UserRecord r;
CountDownLatch l = new CountDownLatch(1);
OnSuccessListener<UserRecord> success = new OnSuccessListener<UserRecord>() {
@Override
void onSuccess(UserRecord result) {
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid())
r = result;
l.countDown()
}
};
OnFailureListener failure = new OnFailureListener() {
@Override
void onFailure(@NonNull Exception e) {
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage())
r = null;
l.countDown()
}
};
if (ThreePidMedium.Email.is(medium)) {
log.info("Performing E-mail 3PID lookup for {}", address)
fbAuth.getUserByEmail(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
log.info("Performing msisdn 3PID lookup for {}", address)
fbAuth.getUserByPhoneNumber(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else {
log.info("{} is not a supported 3PID medium", medium);
r = null;
}
return Optional.ofNullable(r);
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid())
if (urOpt.isPresent()) {
return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get())));
}
return Optional.empty();
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<ThreePidMapping> results = new ArrayList<>();
mappings.parallelStream().forEach(new Consumer<ThreePidMapping>() {
@Override
void accept(ThreePidMapping o) {
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
if (urOpt.isPresent()) {
ThreePidMapping result = new ThreePidMapping();
result.setMedium(o.getMedium())
result.setValue(o.getValue())
result.setMxid(getMxid(urOpt.get()))
results.add(result)
}
}
});
return results;
}
}