Moved OAuth to the external http daemon and added OAuth2 store

This commit is contained in:
Ziver Koc 2021-09-09 17:52:14 +02:00
parent 20228d9e79
commit 28ff89d07f
8 changed files with 114 additions and 53 deletions

View file

@ -18,7 +18,7 @@ subprojects {
apply plugin: 'java-library' apply plugin: 'java-library'
dependencies { dependencies {
implementation 'se.koc:zutil:1.0.302' implementation 'se.koc:zutil:1.0.304'
//implementation 'se.koc:zutil:1.0.0-SNAPSHOT' //implementation 'se.koc:zutil:1.0.0-SNAPSHOT'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'

View file

@ -197,23 +197,11 @@ public class HalServer {
} }
/** /**
* Registers the given page with the external Hal web server. * @return the HTTP server that handles external web pages.
* Note: as this page will most likely be accessible through the internet it needs to be robust and secure.
*
* @param url is the web path to the page.
* @param page is the page to register with the server.
*/ */
public static void registerExternalPage(String url, HttpPage page){ public static HalExternalWebDaemon getExternalWebDaemon(){
httpExternal.setPage(url, page); return httpExternal;
} }
/**
* Registers the given page with the external Hal web server.
* Note: as this page will most likely be accessible through the internet it needs to be robust and secure.
*
* @param page is the page to register with the server.
*/
public static void registerExternalPage(HalWebPage page){
registerExternalPage(page.getId(), page);
}
} }

View file

@ -2,20 +2,27 @@ package se.hal.daemon;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import se.hal.HalContext; import se.hal.HalContext;
import se.hal.HalServer;
import se.hal.intf.HalDaemon; import se.hal.intf.HalDaemon;
import se.hal.intf.HalWebPage;
import se.hal.util.HalAcmeDataStore; import se.hal.util.HalAcmeDataStore;
import se.hal.util.HalOAuth2RegistryStore;
import zutil.log.LogUtil; import zutil.log.LogUtil;
import zutil.net.acme.AcmeClient; import zutil.net.acme.AcmeClient;
import zutil.net.acme.AcmeHttpChallengeFactory; import zutil.net.acme.AcmeHttpChallengeFactory;
import zutil.net.acme.AcmeManualDnsChallengeFactory; import zutil.net.acme.AcmeManualDnsChallengeFactory;
import zutil.net.http.HttpPage; import zutil.net.http.HttpPage;
import zutil.net.http.HttpServer; import zutil.net.http.HttpServer;
import zutil.net.http.page.oauth.OAuth2AuthorizationPage;
import zutil.net.http.page.oauth.OAuth2Registry;
import zutil.net.http.page.oauth.OAuth2TokenPage;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static se.hal.HalContext.CONFIG_HTTP_EXTERNAL_DOMAIN; import static se.hal.HalContext.CONFIG_HTTP_EXTERNAL_DOMAIN;
@ -25,15 +32,33 @@ import static se.hal.HalContext.CONFIG_HTTP_EXTERNAL_PORT;
public class HalExternalWebDaemon implements HalDaemon { public class HalExternalWebDaemon implements HalDaemon {
private static final Logger logger = LogUtil.getLogger(); private static final Logger logger = LogUtil.getLogger();
public static final String ENDPOINT_AUTH = "api/auth/authorize";
public static final String ENDPOINT_TOKEN = "api/auth/token";
// Certificate fields
private HalAcmeDataStore acmeDataStore = new HalAcmeDataStore(); private HalAcmeDataStore acmeDataStore = new HalAcmeDataStore();
private HttpServer httpExternal;
private String externalServerUrl; private String externalServerUrl;
private X509Certificate certificate; private X509Certificate certificate;
// Oauth fields
private OAuth2Registry oAuth2Registry;
// Web server fields
private HttpServer httpExternal;
private HashMap<String, HttpPage> pageMap = new HashMap<>(); private HashMap<String, HttpPage> pageMap = new HashMap<>();
@Override @Override
public void initiate(ScheduledExecutorService executor) { public void initiate(ScheduledExecutorService executor) {
// ------------------------------------
// Initialize Oauth2
// ------------------------------------
oAuth2Registry = new OAuth2Registry(new HalOAuth2RegistryStore());
registerPage(ENDPOINT_AUTH, new OAuth2AuthorizationPage(oAuth2Registry));
registerPage(ENDPOINT_TOKEN, new OAuth2TokenPage(oAuth2Registry));
// ------------------------------------ // ------------------------------------
// Initialize External HttpServer // Initialize External HttpServer
// ------------------------------------ // ------------------------------------
@ -49,7 +74,7 @@ public class HalExternalWebDaemon implements HalDaemon {
logger.warning("Missing '" + CONFIG_HTTP_EXTERNAL_PORT + "' and '" + CONFIG_HTTP_EXTERNAL_DOMAIN + "' configuration, will not setup external http server."); logger.warning("Missing '" + CONFIG_HTTP_EXTERNAL_PORT + "' and '" + CONFIG_HTTP_EXTERNAL_DOMAIN + "' configuration, will not setup external http server.");
} }
} catch (Exception e) { } catch (Exception e) {
logger.severe("Was unable to initiate external web server."); logger.log(Level.SEVERE, "Was unable to initiate external web server.", e);
} }
} }
@ -65,7 +90,7 @@ public class HalExternalWebDaemon implements HalDaemon {
tmpHttpServer = new HttpServer(80); tmpHttpServer = new HttpServer(80);
tmpHttpServer.start(); tmpHttpServer.start();
acme = new AcmeClient(acmeDataStore, new AcmeHttpChallengeFactory(tmpHttpServer)); acme = new AcmeClient(acmeDataStore, new AcmeHttpChallengeFactory(tmpHttpServer), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING);
} else { } else {
throw new IllegalArgumentException("Unknown config value for " + externalServerUrl); throw new IllegalArgumentException("Unknown config value for " + externalServerUrl);
} }
@ -96,10 +121,34 @@ public class HalExternalWebDaemon implements HalDaemon {
} }
public void setPage(String url, HttpPage page) { /**
* @return the OAuth2Registry used by the general OAuth2 authorization pages.
*/
public OAuth2Registry getOAuth2Registry() {
return oAuth2Registry;
}
/**
* Registers the given page with the external Hal web server.
* Note: as this page will most likely be accessible through the internet it needs to be robust and secure.
*
* @param url is the web path to the page.
* @param page is the page to register with the server.
*/
public void registerPage(String url, HttpPage page) {
pageMap.put(url, page); pageMap.put(url, page);
if (httpExternal != null) if (httpExternal != null)
httpExternal.setPage(url, page); httpExternal.setPage(url, page);
} }
/**
* Registers the given page with the external Hal web server.
* Note: as this page will most likely be accessible through the internet it needs to be robust and secure.
*
* @param page is the page to register with the server.
*/
public void registerPage(HalWebPage page){
registerPage(page.getId(), page);
}
} }

View file

@ -21,7 +21,7 @@ public class DeviceDataSqlResult implements SQLResultHandler<HalDeviceData> {
public HalDeviceData handleQueryResult(Statement stmt, ResultSet result) throws SQLException { public HalDeviceData handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
try { try {
if (result.next()) { if (result.next()) {
HalDeviceData dataObj = clazz.newInstance(); HalDeviceData dataObj = clazz.getDeclaredConstructor().newInstance();
dataObj.setData(result.getDouble("data")); dataObj.setData(result.getDouble("data"));
dataObj.setTimestamp(result.getLong("timestamp")); dataObj.setTimestamp(result.getLong("timestamp"));
return dataObj; return dataObj;

View file

@ -0,0 +1,38 @@
package se.hal.util;
import se.hal.HalContext;
import zutil.ObjectUtil;
import zutil.net.http.page.oauth.OAuth2RegistryStore;
import zutil.parser.json.JSONObjectInputStream;
import zutil.parser.json.JSONObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HalOAuth2RegistryStore implements OAuth2RegistryStore {
private static final String CONFIG_HTTP_EXTERNAL_OAUTH2_REGISTRY = "hal_core.http_external.oauth2_registry";
private List<OAuth2ClientRegister> registerList = new ArrayList<>();
@Override
public synchronized List<OAuth2ClientRegister> getClientRegistries() {
String json = HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_OAUTH2_REGISTRY);
if (!ObjectUtil.isEmpty(json)) {
registerList = JSONObjectInputStream.parse(json);
return registerList;
}
return Collections.emptyList();
}
@Override
public synchronized void storeClientRegister(OAuth2ClientRegister register) {
registerList.remove(register);
registerList.add(register);
HalContext.setProperty(CONFIG_HTTP_EXTERNAL_OAUTH2_REGISTRY,
JSONObjectOutputStream.toString(registerList));
}
}

View file

@ -39,14 +39,11 @@ import java.util.logging.Logger;
public class SmartHomeDaemon implements HalDaemon { public class SmartHomeDaemon implements HalDaemon {
private static final Logger logger = LogUtil.getLogger(); private static final Logger logger = LogUtil.getLogger();
public static final String ENDPOINT_AUTH = "api/assistant/google/auth/authorize"; private static final String ENDPOINT_SMARTHOME = "api/assistant/google/smarthome";
public static final String ENDPOINT_TOKEN = "api/assistant/google/auth/token"; protected static final String CONFIG_CLIENT_ID = "hal_assistant.google.client_id";
public static final String ENDPOINT_SMARTHOME = "api/assistant/google/smarthome";
private static final String CONFIG_CLIENT_ID = "hal_assistant.google.client_id";
private SmartHomeImpl smartHome; private SmartHomeImpl smartHome;
private OAuth2Registry oAuth2Registry;
@Override @Override
public void initiate(ScheduledExecutorService executor) { public void initiate(ScheduledExecutorService executor) {
@ -58,13 +55,9 @@ public class SmartHomeDaemon implements HalDaemon {
smartHome = new SmartHomeImpl(); smartHome = new SmartHomeImpl();
oAuth2Registry = new OAuth2Registry(); HalServer.getExternalWebDaemon().getOAuth2Registry().addWhitelist(HalContext.getStringProperty(CONFIG_CLIENT_ID));
oAuth2Registry.addWhitelist(HalContext.getStringProperty(CONFIG_CLIENT_ID)); HalServer.getExternalWebDaemon().getOAuth2Registry().addTokenListener(smartHome);
oAuth2Registry.setTokenListener(smartHome); HalServer.getExternalWebDaemon().registerPage(ENDPOINT_SMARTHOME, new SmartHomePage(smartHome));
HalServer.registerExternalPage(ENDPOINT_AUTH, new OAuth2AuthorizationPage(oAuth2Registry));
HalServer.registerExternalPage(ENDPOINT_TOKEN, new OAuth2TokenPage(oAuth2Registry));
HalServer.registerExternalPage(ENDPOINT_SMARTHOME, new SmartHomePage(smartHome));
} }
} }
} }

View file

@ -34,7 +34,7 @@ import se.hal.struct.Event;
import se.hal.struct.Sensor; import se.hal.struct.Sensor;
import zutil.db.DBConnection; import zutil.db.DBConnection;
import zutil.log.LogUtil; import zutil.log.LogUtil;
import zutil.net.http.page.oauth.OAuth2Registry.TokenRegistrationListener; import zutil.net.http.page.oauth.OAuth2Registry.OAuth2TokenRegistrationListener;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.*;
@ -42,14 +42,14 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class SmartHomeImpl extends SmartHomeApp implements TokenRegistrationListener { public class SmartHomeImpl extends SmartHomeApp implements OAuth2TokenRegistrationListener {
private static final Logger logger = LogUtil.getLogger(); private static final Logger logger = LogUtil.getLogger();
public static final String CONFIG_USER_AGENT = "hal_assistant_google.user_agent"; public static final String CONFIG_USER_AGENT = "hal_assistant_google.user_agent";
public static final String CONFIG_TOKEN = "hal_assistant_google.token";
public static final String CONFIG_TOKEN_TIMEOUT = "hal_assistant_google.token_timeout";
private final String userAgent; private final String userAgent;
private final String clientId;
public SmartHomeImpl() { public SmartHomeImpl() {
@ -57,28 +57,22 @@ public class SmartHomeImpl extends SmartHomeApp implements TokenRegistrationList
HalContext.setProperty(CONFIG_USER_AGENT, "Hal-" + (int) (Math.random() * 10000)); HalContext.setProperty(CONFIG_USER_AGENT, "Hal-" + (int) (Math.random() * 10000));
userAgent = HalContext.getStringProperty(CONFIG_USER_AGENT); userAgent = HalContext.getStringProperty(CONFIG_USER_AGENT);
if (HalContext.containsProperty(CONFIG_TOKEN)) { clientId = HalContext.getStringProperty(SmartHomeDaemon.CONFIG_CLIENT_ID);
// Restore previous token
onTokenRegistration(
null,
HalContext.getStringProperty(CONFIG_TOKEN),
HalContext.getLongProperty(CONFIG_TOKEN_TIMEOUT));
}
} }
@Override @Override
public void onTokenRegistration(String clientId, String token, long timeoutMillis) { public void onTokenRegistration(String clientId, String token, long timeoutMillis) {
if (this.clientId == null || !this.clientId.equals(clientId))
return; // Only assign the token for the Google client ID
try { try {
GoogleCredentials credentials = GoogleCredentials.create(new AccessToken( GoogleCredentials credentials = GoogleCredentials.create(new AccessToken(
token, token,
new Date(System.currentTimeMillis() + timeoutMillis) new Date(timeoutMillis)
)); ));
this.setCredentials(credentials); this.setCredentials(credentials);
logger.fine("New OAuth2 token registered."); logger.fine("New OAuth2 token registered.");
HalContext.setProperty(CONFIG_TOKEN, token);
HalContext.setProperty(CONFIG_TOKEN_TIMEOUT, String.valueOf(timeoutMillis));
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Could not load google credentials", e); logger.log(Level.SEVERE, "Could not load google credentials", e);
} }

View file

@ -1,8 +1,6 @@
package se.hal.plugin.dummy; package se.hal.plugin.dummy;
import se.hal.EventControllerManager;
import se.hal.HalServer; import se.hal.HalServer;
import se.hal.SensorControllerManager;
import se.hal.intf.*; import se.hal.intf.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -51,6 +49,7 @@ public class DummyController implements HalSensorController, HalEventController,
registeredDevices.add((DummyDevice) deviceConfig); registeredDevices.add((DummyDevice) deviceConfig);
} }
@SuppressWarnings("SuspiciousMethodCalls")
@Override @Override
public synchronized void deregister(HalDeviceConfig deviceConfig) { public synchronized void deregister(HalDeviceConfig deviceConfig) {
registeredDevices.remove(deviceConfig); registeredDevices.remove(deviceConfig);
@ -74,6 +73,6 @@ public class DummyController implements HalSensorController, HalEventController,
@Override @Override
public synchronized void close() { public synchronized void close() {
registeredDevices = new ArrayList(); registeredDevices = new ArrayList<>();
} }
} }