mirror of
https://github.com/zyphlar/LanternPowerMonitor.git
synced 2024-03-08 14:07:47 +00:00
Turns out we don't actually need 30MB of bloated jars to make a single HTTP post to get a Google SSO auth token. Don't need them for Firebase either. And not for Apple SSO. Shoot while we're at it, might as well get rid of pi4j too since making a JNI wrapper for PiGPio is easy enough.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package com.lanternsoftware.util.cloudservices.apple;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.lanternsoftware.util.NullUtils;
|
||||
import com.lanternsoftware.util.ResourceLoader;
|
||||
import com.lanternsoftware.util.dao.DaoEntity;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
import com.lanternsoftware.util.http.HttpFactory;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppleSSO {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppleSSO.class);
|
||||
private final Map<String, RSAPublicKey> publicKeys = new HashMap<>();
|
||||
private final String audience;
|
||||
|
||||
public AppleSSO(String _credentialsPath) {
|
||||
audience = ResourceLoader.loadFileAsString(_credentialsPath).trim();
|
||||
}
|
||||
|
||||
public String getEmailFromIdToken(String _idToken) {
|
||||
if (validatePublicKey()) {
|
||||
try {
|
||||
DecodedJWT jwt = JWT.decode(NullUtils.base64ToString(_idToken));
|
||||
String kid = jwt.getHeaderClaim("kid").asString();
|
||||
RSAPublicKey key = publicKeys.get(kid);
|
||||
if (key != null) {
|
||||
Algorithm algorithm = Algorithm.RSA256(key, null);
|
||||
JWTVerifier verifier = JWT.require(algorithm).withIssuer("https://appleid.apple.com").withAudience(audience).build();
|
||||
return verifier.verify(jwt).getClaim("email").asString().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
} catch (Exception _e){
|
||||
LOG.error("Failed to verify Apple JWT token", _e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized boolean validatePublicKey() {
|
||||
if (!publicKeys.isEmpty())
|
||||
return true;
|
||||
DaoEntity resp = DaoSerializer.parse(HttpFactory.pool().executeToString(new HttpGet("https://appleid.apple.com/auth/keys")));
|
||||
for (DaoEntity key : DaoSerializer.getDaoEntityList(resp, "keys")) {
|
||||
try {
|
||||
KeyFactory fact = KeyFactory.getInstance("RSA");
|
||||
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(1, Base64.decodeBase64(DaoSerializer.getString(key, "n"))), new BigInteger(1, Base64.decodeBase64(DaoSerializer.getString(key, "e"))));
|
||||
RSAPublicKey publicKey = (RSAPublicKey)fact.generatePublic(keySpec);
|
||||
if (publicKey != null)
|
||||
publicKeys.put(DaoSerializer.getString(key, "kid"), publicKey);
|
||||
} catch (Exception _e) {
|
||||
LOG.error("Failed to generate RSA public key", _e);
|
||||
}
|
||||
}
|
||||
return !publicKeys.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.lanternsoftware.util.cloudservices.google;
|
||||
|
||||
import com.lanternsoftware.util.dao.annotations.DBSerializable;
|
||||
|
||||
@DBSerializable
|
||||
public class FirebaseCredentials {
|
||||
private String type;
|
||||
private String projectId;
|
||||
private String privateKeyId;
|
||||
private String privateKey;
|
||||
private String clientEmail;
|
||||
private String clientId;
|
||||
private String authUri;
|
||||
private String tokenUri;
|
||||
private String authProviderX509CertUrl;
|
||||
private String clientX509CertUrl;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String _type) {
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String _projectId) {
|
||||
projectId = _projectId;
|
||||
}
|
||||
|
||||
public String getPrivateKeyId() {
|
||||
return privateKeyId;
|
||||
}
|
||||
|
||||
public void setPrivateKeyId(String _privateKeyId) {
|
||||
privateKeyId = _privateKeyId;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String _privateKey) {
|
||||
privateKey = _privateKey;
|
||||
}
|
||||
|
||||
public String getClientEmail() {
|
||||
return clientEmail;
|
||||
}
|
||||
|
||||
public void setClientEmail(String _clientEmail) {
|
||||
clientEmail = _clientEmail;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String _clientId) {
|
||||
clientId = _clientId;
|
||||
}
|
||||
|
||||
public String getAuthUri() {
|
||||
return authUri;
|
||||
}
|
||||
|
||||
public void setAuthUri(String _authUri) {
|
||||
authUri = _authUri;
|
||||
}
|
||||
|
||||
public String getTokenUri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public void setTokenUri(String _tokenUri) {
|
||||
tokenUri = _tokenUri;
|
||||
}
|
||||
|
||||
public String getAuthProviderX509CertUrl() {
|
||||
return authProviderX509CertUrl;
|
||||
}
|
||||
|
||||
public void setAuthProviderX509CertUrl(String _authProviderX509CertUrl) {
|
||||
authProviderX509CertUrl = _authProviderX509CertUrl;
|
||||
}
|
||||
|
||||
public String getClientX509CertUrl() {
|
||||
return clientX509CertUrl;
|
||||
}
|
||||
|
||||
public void setClientX509CertUrl(String _clientX509CertUrl) {
|
||||
clientX509CertUrl = _clientX509CertUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.lanternsoftware.util.cloudservices.google;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.lanternsoftware.util.CollectionUtils;
|
||||
import com.lanternsoftware.util.DateUtils;
|
||||
import com.lanternsoftware.util.NullUtils;
|
||||
import com.lanternsoftware.util.ResourceLoader;
|
||||
import com.lanternsoftware.util.dao.DaoEntity;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
import com.lanternsoftware.util.http.HttpFactory;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class FirebaseHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FirebaseHelper.class);
|
||||
private static final String FCM_SEND_URL = "https://fcm.googleapis.com/v1/projects/%s/messages:send";
|
||||
private static final List<String> SCOPES = List.of("https://www.googleapis.com/auth/firebase.database", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/identitytoolkit", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/datastore");
|
||||
|
||||
private final FirebaseCredentials credentials;
|
||||
private final RSAPrivateKey privateKey;
|
||||
private final String fcmSendUrl;
|
||||
private String accessToken;
|
||||
private Date validUntil;
|
||||
|
||||
public FirebaseHelper(String _credentialsPath) {
|
||||
this(DaoSerializer.parse(ResourceLoader.loadFileAsString(_credentialsPath), FirebaseCredentials.class));
|
||||
}
|
||||
|
||||
public FirebaseHelper(FirebaseCredentials _credentials) {
|
||||
credentials = _credentials;
|
||||
if (credentials != null) {
|
||||
privateKey = fromPEM(credentials.getPrivateKey());
|
||||
fcmSendUrl = String.format(FCM_SEND_URL, credentials.getProjectId());
|
||||
}
|
||||
else {
|
||||
LOG.error("Failed to load FCM credentials");
|
||||
privateKey = null;
|
||||
fcmSendUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private RSAPrivateKey fromPEM(String _pem) {
|
||||
try {
|
||||
String pem = _pem.replaceAll("(-+BEGIN PRIVATE KEY-+|-+END PRIVATE KEY-+|\\r|\\n)", "");
|
||||
byte[] encoded = Base64.decodeBase64(pem);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
return (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
|
||||
}
|
||||
catch (Exception _e) {
|
||||
LOG.error("Failed to generate RSA private key", _e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateAccessToken() {
|
||||
if (isTokenValid())
|
||||
return true;
|
||||
Date now = new Date();
|
||||
String assertion = JWT.create().withKeyId(credentials.getPrivateKeyId()).withIssuer(credentials.getClientEmail()).withIssuedAt(new Date()).withExpiresAt(DateUtils.addHours(now, 1)).withClaim("scope", CollectionUtils.delimit(SCOPES, " ")).withAudience(credentials.getTokenUri()).sign(Algorithm.RSA256(null, privateKey));
|
||||
|
||||
HttpPost post = new HttpPost(credentials.getTokenUri());
|
||||
List<NameValuePair> payload = new ArrayList<>();
|
||||
payload.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
|
||||
payload.add(new BasicNameValuePair("assertion", assertion));
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(payload, StandardCharsets.UTF_8);
|
||||
entity.setContentType("application/x-www-form-urlencoded");
|
||||
post.setEntity(entity);
|
||||
DaoEntity rep = DaoSerializer.parse(HttpFactory.pool().executeToString(post));
|
||||
if (rep == null)
|
||||
return false;
|
||||
accessToken = DaoSerializer.getString(rep, "access_token");
|
||||
validUntil = DateUtils.secondsFromNow(DaoSerializer.getInteger(rep, "expires_in")-10);
|
||||
return isTokenValid();
|
||||
}
|
||||
|
||||
private boolean isTokenValid() {
|
||||
return NullUtils.isNotEmpty(accessToken) && (validUntil != null) && new Date().before(validUntil);
|
||||
}
|
||||
|
||||
public boolean sendMessage(String _deviceToken, DaoEntity _payload) {
|
||||
if (!validateAccessToken()) {
|
||||
LOG.error("Failed to get a valid access token for Firebase, not sending message");
|
||||
return false;
|
||||
}
|
||||
DaoEntity msg = new DaoEntity("message", new DaoEntity("token", _deviceToken).and("data", _payload).and("android", new DaoEntity("priority", "high").and("direct_boot_ok", true)));
|
||||
HttpPost post = new HttpPost(fcmSendUrl);
|
||||
post.addHeader("X-GOOG-API-FORMAT-VERSION", "2");
|
||||
post.addHeader("X-Firebase-Client", "fire-admin-java/8.0.0");
|
||||
post.addHeader("Authorization", "Bearer " + accessToken);
|
||||
post.setEntity(new StringEntity(DaoSerializer.toJson(msg), StandardCharsets.UTF_8));
|
||||
return NullUtils.isNotEmpty(HttpFactory.pool().executeToString(post));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.lanternsoftware.util.cloudservices.google;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.lanternsoftware.util.NullUtils;
|
||||
import com.lanternsoftware.util.ResourceLoader;
|
||||
import com.lanternsoftware.util.dao.DaoEntity;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
import com.lanternsoftware.util.http.HttpFactory;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GoogleSSO {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GoogleSSO.class);
|
||||
private final String googleClientId;
|
||||
private final String googleClientSecret;
|
||||
|
||||
public GoogleSSO(String _credentialsPath) {
|
||||
DaoEntity google = DaoSerializer.parse(ResourceLoader.loadFileAsString(_credentialsPath));
|
||||
googleClientId = DaoSerializer.getString(google, "id");
|
||||
googleClientSecret = DaoSerializer.getString(google, "secret");
|
||||
}
|
||||
|
||||
public String signin(String _code) {
|
||||
HttpPost post = new HttpPost("https://oauth2.googleapis.com/token");
|
||||
List<NameValuePair> payload = new ArrayList<>();
|
||||
payload.add(new BasicNameValuePair("grant_type", "authorization_code"));
|
||||
payload.add(new BasicNameValuePair("code", _code));
|
||||
payload.add(new BasicNameValuePair("redirect_uri", "https://lanternsoftware.com/console"));
|
||||
payload.add(new BasicNameValuePair("client_id", googleClientId));
|
||||
payload.add(new BasicNameValuePair("client_secret", googleClientSecret));
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(payload, StandardCharsets.UTF_8);
|
||||
entity.setContentType("application/x-www-form-urlencoded");
|
||||
post.setEntity(entity);
|
||||
post.setHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
String idToken = DaoSerializer.getString(DaoSerializer.parse(HttpFactory.pool().executeToString(post)), "id_token");
|
||||
if (NullUtils.isNotEmpty(idToken)) {
|
||||
try {
|
||||
DecodedJWT jwt = JWT.decode(idToken);
|
||||
return DaoSerializer.getString(DaoSerializer.parse(NullUtils.base64ToString(jwt.getPayload())), "email");
|
||||
} catch (Exception _e) {
|
||||
logger.error("Failed to validate google auth code", _e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
logger.error("Failed to validate google auth code");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.lanternsoftware.util.cloudservices.google.dao;
|
||||
|
||||
import com.lanternsoftware.util.cloudservices.google.FirebaseCredentials;
|
||||
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
|
||||
import com.lanternsoftware.util.dao.DaoEntity;
|
||||
import com.lanternsoftware.util.dao.DaoProxyType;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FirebaseCredentialsSerializer extends AbstractDaoSerializer<FirebaseCredentials>
|
||||
{
|
||||
@Override
|
||||
public Class<FirebaseCredentials> getSupportedClass()
|
||||
{
|
||||
return FirebaseCredentials.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DaoProxyType> getSupportedProxies() {
|
||||
return Collections.singletonList(DaoProxyType.MONGO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoEntity toDaoEntity(FirebaseCredentials _o)
|
||||
{
|
||||
DaoEntity d = new DaoEntity();
|
||||
d.put("type", _o.getType());
|
||||
d.put("project_id", _o.getProjectId());
|
||||
d.put("private_key_id", _o.getPrivateKeyId());
|
||||
d.put("private_key", _o.getPrivateKey());
|
||||
d.put("client_email", _o.getClientEmail());
|
||||
d.put("client_id", _o.getClientId());
|
||||
d.put("auth_uri", _o.getAuthUri());
|
||||
d.put("token_uri", _o.getTokenUri());
|
||||
d.put("auth_provider_x509_cert_url", _o.getAuthProviderX509CertUrl());
|
||||
d.put("client_x509_cert_url", _o.getClientX509CertUrl());
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FirebaseCredentials fromDaoEntity(DaoEntity _d)
|
||||
{
|
||||
FirebaseCredentials o = new FirebaseCredentials();
|
||||
o.setType(DaoSerializer.getString(_d, "type"));
|
||||
o.setProjectId(DaoSerializer.getString(_d, "project_id"));
|
||||
o.setPrivateKeyId(DaoSerializer.getString(_d, "private_key_id"));
|
||||
o.setPrivateKey(DaoSerializer.getString(_d, "private_key"));
|
||||
o.setClientEmail(DaoSerializer.getString(_d, "client_email"));
|
||||
o.setClientId(DaoSerializer.getString(_d, "client_id"));
|
||||
o.setAuthUri(DaoSerializer.getString(_d, "auth_uri"));
|
||||
o.setTokenUri(DaoSerializer.getString(_d, "token_uri"));
|
||||
o.setAuthProviderX509CertUrl(DaoSerializer.getString(_d, "auth_provider_x509_cert_url"));
|
||||
o.setClientX509CertUrl(DaoSerializer.getString(_d, "client_x509_cert_url"));
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.lanternsoftware.util.cloudservices.google.dao.FirebaseCredentialsSerializer
|
||||
Reference in New Issue
Block a user