diff --git a/build.gradle b/build.gradle index a49ccd8a..2a276ff1 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ subprojects { apply plugin: 'java-library' dependencies { - implementation 'se.koc:zutil:1.0.302' + implementation 'se.koc:zutil:1.0.304' //implementation 'se.koc:zutil:1.0.0-SNAPSHOT' testImplementation 'junit:junit:4.12' diff --git a/hal-core/src/se/hal/HalServer.java b/hal-core/src/se/hal/HalServer.java index dbf0fa22..6b4dcb36 100644 --- a/hal-core/src/se/hal/HalServer.java +++ b/hal-core/src/se/hal/HalServer.java @@ -197,23 +197,11 @@ public class HalServer { } /** - * 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. + * @return the HTTP server that handles external web pages. */ - public static void registerExternalPage(String url, HttpPage page){ - httpExternal.setPage(url, page); + public static HalExternalWebDaemon getExternalWebDaemon(){ + 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); - } + } diff --git a/hal-core/src/se/hal/daemon/HalExternalWebDaemon.java b/hal-core/src/se/hal/daemon/HalExternalWebDaemon.java index c69231b0..960a8491 100644 --- a/hal-core/src/se/hal/daemon/HalExternalWebDaemon.java +++ b/hal-core/src/se/hal/daemon/HalExternalWebDaemon.java @@ -2,20 +2,27 @@ package se.hal.daemon; import org.shredzone.acme4j.exception.AcmeException; import se.hal.HalContext; +import se.hal.HalServer; import se.hal.intf.HalDaemon; +import se.hal.intf.HalWebPage; import se.hal.util.HalAcmeDataStore; +import se.hal.util.HalOAuth2RegistryStore; import zutil.log.LogUtil; import zutil.net.acme.AcmeClient; import zutil.net.acme.AcmeHttpChallengeFactory; import zutil.net.acme.AcmeManualDnsChallengeFactory; import zutil.net.http.HttpPage; 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.security.GeneralSecurityException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; import java.util.logging.Logger; 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 { 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 HttpServer httpExternal; private String externalServerUrl; private X509Certificate certificate; + + // Oauth fields + private OAuth2Registry oAuth2Registry; + + // Web server fields + private HttpServer httpExternal; private HashMap pageMap = new HashMap<>(); @Override 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 // ------------------------------------ @@ -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."); } } 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.start(); - acme = new AcmeClient(acmeDataStore, new AcmeHttpChallengeFactory(tmpHttpServer)); + acme = new AcmeClient(acmeDataStore, new AcmeHttpChallengeFactory(tmpHttpServer), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING); } else { 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); if (httpExternal != null) 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); + } } diff --git a/hal-core/src/se/hal/util/DeviceDataSqlResult.java b/hal-core/src/se/hal/util/DeviceDataSqlResult.java index 82b62389..bf0814fc 100644 --- a/hal-core/src/se/hal/util/DeviceDataSqlResult.java +++ b/hal-core/src/se/hal/util/DeviceDataSqlResult.java @@ -21,7 +21,7 @@ public class DeviceDataSqlResult implements SQLResultHandler { public HalDeviceData handleQueryResult(Statement stmt, ResultSet result) throws SQLException { try { if (result.next()) { - HalDeviceData dataObj = clazz.newInstance(); + HalDeviceData dataObj = clazz.getDeclaredConstructor().newInstance(); dataObj.setData(result.getDouble("data")); dataObj.setTimestamp(result.getLong("timestamp")); return dataObj; diff --git a/hal-core/src/se/hal/util/HalOAuth2RegistryStore.java b/hal-core/src/se/hal/util/HalOAuth2RegistryStore.java new file mode 100644 index 00000000..5c583b10 --- /dev/null +++ b/hal-core/src/se/hal/util/HalOAuth2RegistryStore.java @@ -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 registerList = new ArrayList<>(); + + + @Override + public synchronized List 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)); + } +} diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDaemon.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDaemon.java index 411ec8e8..4c286737 100644 --- a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDaemon.java +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeDaemon.java @@ -39,14 +39,11 @@ import java.util.logging.Logger; public class SmartHomeDaemon implements HalDaemon { private static final Logger logger = LogUtil.getLogger(); - public static final String ENDPOINT_AUTH = "api/assistant/google/auth/authorize"; - public static final String ENDPOINT_TOKEN = "api/assistant/google/auth/token"; - public static final String ENDPOINT_SMARTHOME = "api/assistant/google/smarthome"; - - private static final String CONFIG_CLIENT_ID = "hal_assistant.google.client_id"; + private static final String ENDPOINT_SMARTHOME = "api/assistant/google/smarthome"; + protected static final String CONFIG_CLIENT_ID = "hal_assistant.google.client_id"; private SmartHomeImpl smartHome; - private OAuth2Registry oAuth2Registry; + @Override public void initiate(ScheduledExecutorService executor) { @@ -58,13 +55,9 @@ public class SmartHomeDaemon implements HalDaemon { smartHome = new SmartHomeImpl(); - oAuth2Registry = new OAuth2Registry(); - oAuth2Registry.addWhitelist(HalContext.getStringProperty(CONFIG_CLIENT_ID)); - oAuth2Registry.setTokenListener(smartHome); - - HalServer.registerExternalPage(ENDPOINT_AUTH, new OAuth2AuthorizationPage(oAuth2Registry)); - HalServer.registerExternalPage(ENDPOINT_TOKEN, new OAuth2TokenPage(oAuth2Registry)); - HalServer.registerExternalPage(ENDPOINT_SMARTHOME, new SmartHomePage(smartHome)); + HalServer.getExternalWebDaemon().getOAuth2Registry().addWhitelist(HalContext.getStringProperty(CONFIG_CLIENT_ID)); + HalServer.getExternalWebDaemon().getOAuth2Registry().addTokenListener(smartHome); + HalServer.getExternalWebDaemon().registerPage(ENDPOINT_SMARTHOME, new SmartHomePage(smartHome)); } } } diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeImpl.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeImpl.java index 4c753498..39b36f4b 100644 --- a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeImpl.java +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/SmartHomeImpl.java @@ -34,7 +34,7 @@ import se.hal.struct.Event; import se.hal.struct.Sensor; import zutil.db.DBConnection; 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.util.*; @@ -42,14 +42,14 @@ import java.util.logging.Level; 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(); 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 clientId; public SmartHomeImpl() { @@ -57,28 +57,22 @@ public class SmartHomeImpl extends SmartHomeApp implements TokenRegistrationList HalContext.setProperty(CONFIG_USER_AGENT, "Hal-" + (int) (Math.random() * 10000)); userAgent = HalContext.getStringProperty(CONFIG_USER_AGENT); - if (HalContext.containsProperty(CONFIG_TOKEN)) { - // Restore previous token - onTokenRegistration( - null, - HalContext.getStringProperty(CONFIG_TOKEN), - HalContext.getLongProperty(CONFIG_TOKEN_TIMEOUT)); - } + clientId = HalContext.getStringProperty(SmartHomeDaemon.CONFIG_CLIENT_ID); } @Override 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 { GoogleCredentials credentials = GoogleCredentials.create(new AccessToken( token, - new Date(System.currentTimeMillis() + timeoutMillis) + new Date(timeoutMillis) )); this.setCredentials(credentials); logger.fine("New OAuth2 token registered."); - - HalContext.setProperty(CONFIG_TOKEN, token); - HalContext.setProperty(CONFIG_TOKEN_TIMEOUT, String.valueOf(timeoutMillis)); } catch (Exception e) { logger.log(Level.SEVERE, "Could not load google credentials", e); } diff --git a/plugins/hal-dummy/src/se/hal/plugin/dummy/DummyController.java b/plugins/hal-dummy/src/se/hal/plugin/dummy/DummyController.java index c8c9c426..7de8dae2 100644 --- a/plugins/hal-dummy/src/se/hal/plugin/dummy/DummyController.java +++ b/plugins/hal-dummy/src/se/hal/plugin/dummy/DummyController.java @@ -1,8 +1,6 @@ package se.hal.plugin.dummy; -import se.hal.EventControllerManager; import se.hal.HalServer; -import se.hal.SensorControllerManager; import se.hal.intf.*; import java.util.ArrayList; @@ -51,6 +49,7 @@ public class DummyController implements HalSensorController, HalEventController, registeredDevices.add((DummyDevice) deviceConfig); } + @SuppressWarnings("SuspiciousMethodCalls") @Override public synchronized void deregister(HalDeviceConfig deviceConfig) { registeredDevices.remove(deviceConfig); @@ -74,6 +73,6 @@ public class DummyController implements HalSensorController, HalEventController, @Override public synchronized void close() { - registeredDevices = new ArrayList(); + registeredDevices = new ArrayList<>(); } }