diff --git a/hal.conf.example b/hal.conf.example index 48472e00..89561b76 100755 --- a/hal.conf.example +++ b/hal.conf.example @@ -36,5 +36,3 @@ powerchallenge.sync_port=6666 ## Google Assistant assistant.google.port=8081 -assistant.google.keystore=resource/conf/selfsigned.jks -assistant.google.keystore_psw=123456789 \ No newline at end of file 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 780e9d84..a66715c8 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 @@ -26,8 +26,11 @@ package se.hal.plugin.assistant.google; import se.hal.HalContext; import se.hal.intf.HalDaemon; -import zutil.io.file.FileUtil; +import se.hal.plugin.assistant.google.endpoint.OAuth2AuthPage; +import se.hal.plugin.assistant.google.endpoint.OAuth2TokenPage; +import se.hal.plugin.assistant.google.endpoint.SmartHomePage; import zutil.log.LogUtil; +import zutil.net.http.HttpServer; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; @@ -41,6 +44,7 @@ public class SmartHomeDaemon implements HalDaemon { private static final String PARAM_KEYSTORE_PASSWORD = "assistant.google.keystore_psw"; private static final String PARAM_GOOGLE_CREDENTIALS = "assistant.google.credentials"; + private HttpServer httpServer; private SmartHomeImpl smartHome; @Override @@ -54,11 +58,14 @@ public class SmartHomeDaemon implements HalDaemon { } smartHome = new SmartHomeImpl( - HalContext.getIntegerProperty(PARAM_PORT), - FileUtil.find(HalContext.RESOURCE_ROOT + "/" + HalContext.getStringProperty(PARAM_KEYSTORE_PATH)), - HalContext.getStringProperty(PARAM_KEYSTORE_PASSWORD), HalContext.RESOURCE_ROOT + "/" + HalContext.getStringProperty(PARAM_GOOGLE_CREDENTIALS) ); + + httpServer = new HttpServer(HalContext.getIntegerProperty(PARAM_PORT)); + httpServer.setPage(OAuth2AuthPage.ENDPOINT_URL, new OAuth2AuthPage(smartHome)); + httpServer.setPage(OAuth2TokenPage.ENDPOINT_URL, new OAuth2TokenPage(smartHome)); + httpServer.setPage(SmartHomePage.ENDPOINT_URL, new SmartHomePage(smartHome)); + httpServer.start(); } } 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 c89ea324..85f23039 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 @@ -16,7 +16,6 @@ package se.hal.plugin.assistant.google; -import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -24,27 +23,14 @@ import java.util.Map; import java.util.logging.Logger; import com.google.actions.api.smarthome.*; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.gson.Gson; -import com.google.home.graph.v1.DeviceProto; -import com.google.protobuf.Struct; -import com.google.protobuf.util.JsonFormat; -import se.hal.HalContext; -import se.hal.plugin.assistant.google.endpoint.AuthServlet; -import se.hal.plugin.assistant.google.endpoint.AuthTokenServlet; -import se.hal.plugin.assistant.google.endpoint.SmartHomeServlet; -import zutil.io.file.FileUtil; import zutil.log.LogUtil; -import zutil.net.http.HttpServer; public class SmartHomeImpl extends SmartHomeApp { private static final Logger logger = LogUtil.getLogger(); - private HttpServer httpServer; - - public SmartHomeImpl(int port, File cert, String certPass, String googleCredentials) { + public SmartHomeImpl(String googleCredentials) { /*try { GoogleCredentials credentials = GoogleCredentials.fromStream(FileUtil.getInputStream(googleCredentials)); this.setCredentials(credentials); @@ -52,13 +38,6 @@ public class SmartHomeImpl extends SmartHomeApp { logger.severe("Could not load google credentials"); throw new RuntimeException(e); }*/ - - //httpServer = new HttpServer(port, cert, certPass); - httpServer = new HttpServer(port); - httpServer.setPage(AuthServlet.ENDPOINT_URL, new AuthServlet(this)); - httpServer.setPage(AuthTokenServlet.ENDPOINT_URL, new AuthTokenServlet(this)); - httpServer.setPage(SmartHomeServlet.ENDPOINT_URL, new SmartHomeServlet(this)); - httpServer.start(); } diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2AuthPage.java similarity index 65% rename from plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthServlet.java rename to plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2AuthPage.java index 377b880d..48d62c97 100644 --- a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthServlet.java +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2AuthPage.java @@ -44,18 +44,24 @@ import se.hal.plugin.assistant.google.SmartHomeImpl; import zutil.net.http.HttpHeader; import zutil.net.http.HttpPage; import zutil.net.http.HttpPrintStream; +import zutil.net.http.HttpURL; +import java.net.MalformedURLException; import java.net.URLDecoder; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Map; - -public class AuthServlet implements HttpPage { +/** + * This endpoint is the first step in the OAuth 2 procedure. + * The purpose of this page is get authorization from the user to share a resource. + */ +public class OAuth2AuthPage implements HttpPage { public static final String ENDPOINT_URL = "api/assistant/google/auth/authorize"; + protected static final String SECRET_CODE = "SUPER-SECURE-CODE"; - public AuthServlet(SmartHomeImpl smartHome) {} + + public OAuth2AuthPage(SmartHomeImpl smartHome) {} @Override @@ -64,20 +70,32 @@ public class AuthServlet implements HttpPage { HttpHeader headers, Map session, Map cookie, - Map request) { + Map request) throws MalformedURLException { - if (request.containsKey("redirect_uri")) { - StringBuilder redirectURL = new StringBuilder(); - redirectURL.append(URLDecoder.decode(request.get("redirect_uri"), StandardCharsets.UTF_8)); - redirectURL.append("?"); - redirectURL.append("code=").append("xxxxxx"); - redirectURL.append("state=").append(request.get("state")); - - out.setResponseStatusCode(302); - out.setHeader("Location", URLEncoder.encode("/login?responseurl=" + redirectURL.toString(), StandardCharsets.UTF_8)); - } else { + if (!request.containsKey("redirect_uri")) { out.setResponseStatusCode(400); out.println("400: Bad Request, missing property: redirect_uri"); + return; + } + + HttpURL url = new HttpURL(URLDecoder.decode(request.get("redirect_uri"), StandardCharsets.UTF_8)); + + if (!"HTTPS".equalsIgnoreCase(url.getProtocol())) { + out.setResponseStatusCode(400); + out.println("Bad redirect protocol: " + url.getProtocol()); + return; + } + + if ("code".equals(request.get("response_type"))) { // response types: code, password, client_credentials + url.setParameter("state", request.get("state")); + url.setParameter("code", SECRET_CODE); + + out.setResponseStatusCode(302); + out.setHeader(HttpHeader.HEADER_LOCATION, url.toString()); + } else { + out.setResponseStatusCode(400); + out.println("400: Bad Request, unsupported response_type: " + request.get("response_type")); + return; } } } diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthTokenServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2TokenPage.java similarity index 71% rename from plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthTokenServlet.java rename to plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2TokenPage.java index 2b4b0c7a..f2d6176a 100644 --- a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/AuthTokenServlet.java +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/OAuth2TokenPage.java @@ -48,13 +48,18 @@ import zutil.net.http.HttpPrintStream; import zutil.net.http.page.HttpJsonPage; import zutil.parser.DataNode; - -public class AuthTokenServlet extends HttpJsonPage { +/** + * This endpoint is the second step in the OAuth 2 procedure. + * The purpose of this page is give a token that should be used for all consequent HTTP. + */ +public class OAuth2TokenPage extends HttpJsonPage { private static final int SECONDS_IN_DAY = 86400; public static final String ENDPOINT_URL = "api/assistant/google/auth/token"; + protected final String ACCESS_TOKEN = "SUPER-SECURE-TOKEN"; - public AuthTokenServlet(SmartHomeImpl smartHome) {} + + public OAuth2TokenPage(SmartHomeImpl smartHome) {} @Override @@ -65,19 +70,35 @@ public class AuthTokenServlet extends HttpJsonPage { Map cookie, Map request) { - String grantType = request.get("grant_type"); - - DataNode jsonRes = new DataNode(DataNode.DataType.Map); - jsonRes.set("token_type", "bearer"); - jsonRes.set("access_token", "123access"); - jsonRes.set("expires_in", SECONDS_IN_DAY); - - if (grantType.equals("authorization_code")) { - jsonRes.set("refresh_token", "123refresh"); - } + // POST out.setHeader("Access-Control-Allow-Origin", "*"); out.setHeader("Pragma", "no-cache"); + + DataNode jsonRes = new DataNode(DataNode.DataType.Map); + + if (OAuth2AuthPage.SECRET_CODE.equals(request.get("code"))) { + jsonRes.set("refresh_token", "123refresh"); + } else { + out.setResponseStatusCode(400); + DataNode jsonErr = new DataNode(DataNode.DataType.Map); + jsonRes.set("error", "Invalid code value provided."); + return jsonErr; + } + + if ("authorization_code".equals(request.get("grant_type"))) { + jsonRes.set("refresh_token", "123refresh"); + } else { + out.setResponseStatusCode(400); + DataNode jsonErr = new DataNode(DataNode.DataType.Map); + jsonRes.set("error", "Unsupported grant_type: " + request.containsKey("grant_type")); + return jsonErr; + } + + jsonRes.set("access_token", ACCESS_TOKEN); + jsonRes.set("token_type", "bearer"); + jsonRes.set("expires_in", SECONDS_IN_DAY); + return jsonRes; } } diff --git a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomePage.java similarity index 97% rename from plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java rename to plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomePage.java index 86442e2c..c7e7d1d3 100644 --- a/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomeServlet.java +++ b/plugins/hal-assistant-google/src/se/hal/plugin/assistant/google/endpoint/SmartHomePage.java @@ -59,14 +59,14 @@ import zutil.net.http.HttpPrintStream; * handling in Google App * Engine](https://cloud.google.com/appengine/docs/standard/java/how-requests-are-handled). */ -public class SmartHomeServlet implements HttpPage { +public class SmartHomePage implements HttpPage { private static final Logger logger = LogUtil.getLogger(); public static final String ENDPOINT_URL = "api/assistant/google/smarthome"; private SmartHomeImpl smartHome; - public SmartHomeServlet(SmartHomeImpl smartHome) { + public SmartHomePage(SmartHomeImpl smartHome) { this.smartHome = smartHome; }