Migrated to new Oauth2 pages in Zutil
This commit is contained in:
parent
baaeddb9fb
commit
03ae4329bc
5 changed files with 33 additions and 334 deletions
|
|
@ -26,12 +26,14 @@ package se.hal.plugin.assistant.google;
|
|||
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalDaemon;
|
||||
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 zutil.net.http.page.oauth.OAuth2AuthorizationPage;
|
||||
import zutil.net.http.page.oauth.OAuth2Registry;
|
||||
import zutil.net.http.page.oauth.OAuth2TokenPage;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
@ -39,32 +41,33 @@ import java.util.logging.Logger;
|
|||
public class SmartHomeDaemon implements HalDaemon {
|
||||
private static final Logger logger = LogUtil.getLogger();
|
||||
|
||||
private static final String PARAM_PORT = "assistant.google.port";
|
||||
private static final String PARAM_KEYSTORE_PATH = "assistant.google.keystore";
|
||||
private static final String PARAM_KEYSTORE_PASSWORD = "assistant.google.keystore_psw";
|
||||
private static final String PARAM_GOOGLE_CREDENTIALS = "assistant.google.credentials";
|
||||
public static final String ENDPOINT_AUTH = "api/assistant/google/auth/token";
|
||||
public static final String ENDPOINT_TOKEN = "api/assistant/google/auth/authorize";
|
||||
|
||||
private static final String PARAM_PORT = "assistant.google.port";
|
||||
private static final String PARAM_CLIENT_ID = "assistant.google.client_id";
|
||||
|
||||
private HttpServer httpServer;
|
||||
private SmartHomeImpl smartHome;
|
||||
private OAuth2Registry oAuth2Registry;
|
||||
private HttpServer httpServer;
|
||||
|
||||
@Override
|
||||
public void initiate(ScheduledExecutorService executor) {
|
||||
if (smartHome == null) {
|
||||
if (!HalContext.containsProperty(PARAM_PORT) ||
|
||||
!HalContext.containsProperty(PARAM_KEYSTORE_PATH) ||
|
||||
!HalContext.containsProperty(PARAM_KEYSTORE_PASSWORD) ||
|
||||
!HalContext.containsProperty(PARAM_GOOGLE_CREDENTIALS)) {
|
||||
!HalContext.containsProperty(PARAM_CLIENT_ID)) {
|
||||
logger.severe("Missing configuration, abort initializations.");
|
||||
return;
|
||||
}
|
||||
|
||||
smartHome = new SmartHomeImpl(
|
||||
HalContext.RESOURCE_ROOT + "/" + HalContext.getStringProperty(PARAM_GOOGLE_CREDENTIALS)
|
||||
);
|
||||
smartHome = new SmartHomeImpl("token", new Date(System.currentTimeMillis() + 24*60*60*1000));
|
||||
|
||||
oAuth2Registry = new OAuth2Registry();
|
||||
oAuth2Registry.addWhitelist(HalContext.getStringProperty(PARAM_CLIENT_ID));
|
||||
|
||||
httpServer = new HttpServer(HalContext.getIntegerProperty(PARAM_PORT));
|
||||
httpServer.setPage(OAuth2AuthPage.ENDPOINT_URL, new OAuth2AuthPage(smartHome,
|
||||
"https://oauth-redirect.googleusercontent.com/r/optimal-comfort-93608"));
|
||||
httpServer.setPage(OAuth2TokenPage.ENDPOINT_URL, new OAuth2TokenPage(smartHome));
|
||||
httpServer.setPage(ENDPOINT_AUTH, new OAuth2AuthorizationPage(oAuth2Registry));
|
||||
httpServer.setPage(ENDPOINT_TOKEN, new OAuth2TokenPage(oAuth2Registry));
|
||||
httpServer.setPage(SmartHomePage.ENDPOINT_URL, new SmartHomePage(smartHome));
|
||||
httpServer.start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@
|
|||
|
||||
package se.hal.plugin.assistant.google;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.actions.api.smarthome.*;
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import zutil.log.LogUtil;
|
||||
|
||||
|
||||
|
|
@ -30,9 +29,9 @@ public class SmartHomeImpl extends SmartHomeApp {
|
|||
private static final Logger logger = LogUtil.getLogger();
|
||||
|
||||
|
||||
public SmartHomeImpl(String googleCredentials) {
|
||||
/*try {
|
||||
GoogleCredentials credentials = GoogleCredentials.fromStream(FileUtil.getInputStream(googleCredentials));
|
||||
public SmartHomeImpl(String accessToken, Date accessExpirationTime) {
|
||||
/* try {
|
||||
GoogleCredentials credentials = GoogleCredentials.create(new AccessToken(accessToken, accessExpirationTime));
|
||||
this.setCredentials(credentials);
|
||||
} catch (Exception e) {
|
||||
logger.severe("Could not load google credentials");
|
||||
|
|
@ -43,6 +42,8 @@ public class SmartHomeImpl extends SmartHomeApp {
|
|||
|
||||
@Override
|
||||
public SyncResponse onSync(SyncRequest syncRequest, Map<?, ?> headers) {
|
||||
logger.fine("Received sync request.");
|
||||
|
||||
SyncResponse res = new SyncResponse();
|
||||
res.setRequestId(syncRequest.requestId);
|
||||
res.setPayload(new SyncResponse.Payload());
|
||||
|
|
@ -100,6 +101,8 @@ public class SmartHomeImpl extends SmartHomeApp {
|
|||
|
||||
@Override
|
||||
public QueryResponse onQuery(QueryRequest queryRequest, Map<?, ?> headers) {
|
||||
logger.fine("Received query request.");
|
||||
|
||||
QueryRequest.Inputs.Payload.Device[] devices = ((QueryRequest.Inputs) queryRequest.getInputs()[0]).payload.devices;
|
||||
QueryResponse res = new QueryResponse();
|
||||
res.setRequestId(queryRequest.requestId);
|
||||
|
|
@ -125,6 +128,8 @@ public class SmartHomeImpl extends SmartHomeApp {
|
|||
|
||||
@Override
|
||||
public ExecuteResponse onExecute(ExecuteRequest executeRequest, Map<?, ?> headers) {
|
||||
logger.fine("Received execute request.");
|
||||
|
||||
ExecuteResponse res = new ExecuteResponse();
|
||||
|
||||
List<ExecuteResponse.Payload.Commands> commandsResponse = new ArrayList<>();
|
||||
|
|
@ -218,6 +223,6 @@ public class SmartHomeImpl extends SmartHomeApp {
|
|||
|
||||
@Override
|
||||
public void onDisconnect(DisconnectRequest disconnectRequest, Map<?, ?> headers) {
|
||||
|
||||
logger.fine("Received disconnect request.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package se.hal.plugin.assistant.google.endpoint;
|
||||
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* RFC 6749: Chapter 4.1.2:
|
||||
* https://tools.ietf.org/html/rfc6749
|
||||
*/
|
||||
public class OAuth2AuthPage implements HttpPage {
|
||||
public static final String ENDPOINT_URL = "api/assistant/google/auth/authorize";
|
||||
|
||||
/** The request is missing a required parameter, includes an invalid parameter value, includes a parameter
|
||||
more than once, or is otherwise malformed. **/
|
||||
protected static final String ERROR_INVALID_REQUEST = "invalid_request";
|
||||
/** The client is not authorized to request an authorization code using this method. **/
|
||||
protected static final String ERROR_UNAUTHORIZED_CLIENT = "unauthorized_client";
|
||||
/** The resource owner or authorization server denied the request. **/
|
||||
protected static final String ERROR_ACCESS_DENIED = "access_denied";
|
||||
/** The authorization server does not support obtaining an authorization code using this method. **/
|
||||
protected static final String ERROR_UNSUPPORTED_RESP_TYPE = "unsupported_response_type";
|
||||
/** The requested scope is invalid, unknown, or malformed. **/
|
||||
protected static final String ERROR_INVALID_SCOPE = "invalid_scope";
|
||||
/** The authorization server encountered an unexpected condition that prevented it from fulfilling the request.
|
||||
(This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client
|
||||
via an HTTP redirect.) **/
|
||||
protected static final String ERROR_SERVER_ERROR = "server_error";
|
||||
/** The authorization server is currently unable to handle the request due to a temporary overloading or maintenance
|
||||
of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned
|
||||
to the client via an HTTP redirect.) **/
|
||||
protected static final String ERROR_TEMPORARILY_UNAVAILABLE = "temporarily_unavailable";
|
||||
|
||||
private static final String RESPONSE_TYPE_CODE = "code";
|
||||
private static final String RESPONSE_TYPE_PASSWORD = "password";
|
||||
private static final String RESPONSE_TYPE_CREDENTIALS = "client_credentials";
|
||||
|
||||
protected static final String SECRET_CODE = "SUPER-SECURE-CODE";
|
||||
|
||||
|
||||
private String clientId;
|
||||
|
||||
|
||||
public OAuth2AuthPage(SmartHomeImpl smartHome) {}
|
||||
public OAuth2AuthPage(SmartHomeImpl smartHome, String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void respond(
|
||||
HttpPrintStream out,
|
||||
HttpHeader headers,
|
||||
Map<String, Object> session,
|
||||
Map<String, String> cookie,
|
||||
Map<String, String> request) throws MalformedURLException {
|
||||
|
||||
if (!request.containsKey("redirect_uri")) {
|
||||
errorResponse(out, "Bad Request, missing property: redirect_uri");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!request.containsKey("client_id") || clientId != null && clientId.equals(request.containsKey("client_id"))) {
|
||||
errorResponse(out, "Bad Request, missing or invalid client_id property.");
|
||||
return;
|
||||
}
|
||||
|
||||
HttpURL url = new HttpURL(URLDecoder.decode(request.get("redirect_uri"), StandardCharsets.UTF_8));
|
||||
|
||||
if (!"HTTPS".equalsIgnoreCase(url.getProtocol())) {
|
||||
errorResponse(out, "Bad redirect protocol: " + url.getProtocol());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (request.get("response_type")) {
|
||||
case RESPONSE_TYPE_CODE:
|
||||
url.setParameter("state", request.get("state"));
|
||||
url.setParameter("code", SECRET_CODE);
|
||||
break;
|
||||
case RESPONSE_TYPE_PASSWORD:
|
||||
case RESPONSE_TYPE_CREDENTIALS:
|
||||
default:
|
||||
errorRedirect(out, url, ERROR_INVALID_REQUEST, request.get("state"),
|
||||
"unsupported response_type: " + request.get("response_type"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the redirect
|
||||
|
||||
redirect(out, url);
|
||||
}
|
||||
|
||||
|
||||
private static void errorResponse(HttpPrintStream out, String description) {
|
||||
out.setResponseStatusCode(400);
|
||||
out.println(description);
|
||||
}
|
||||
|
||||
private static void errorRedirect(HttpPrintStream out, HttpURL url, String error, String state, String description) {
|
||||
out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded");
|
||||
url.setParameter("error", error);
|
||||
if (description != null) url.setParameter("error_description", description);
|
||||
//if (uri != null) url.setParameter("error_uri", uri);
|
||||
if (state != null) url.setParameter("state", state);
|
||||
|
||||
redirect(out, url);
|
||||
}
|
||||
|
||||
private static void redirect(HttpPrintStream out, HttpURL url) {
|
||||
out.setResponseStatusCode(302);
|
||||
out.setHeader(HttpHeader.HEADER_LOCATION, url.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package se.hal.plugin.assistant.google.endpoint;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import se.hal.plugin.assistant.google.SmartHomeImpl;
|
||||
import zutil.net.http.HttpHeader;
|
||||
import zutil.net.http.HttpPrintStream;
|
||||
import zutil.net.http.page.HttpJsonPage;
|
||||
import zutil.parser.DataNode;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* RFC 6749: Chapter 4.1
|
||||
*/
|
||||
public class OAuth2TokenPage extends HttpJsonPage {
|
||||
private static final int SECONDS_IN_DAY = 86400;
|
||||
public static final String ENDPOINT_URL = "api/assistant/google/auth/token";
|
||||
|
||||
/** The request is missing a required parameter, includes an unsupported parameter value (other than grant type),
|
||||
repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the
|
||||
client, or is otherwise malformed. **/
|
||||
protected static final String ERROR_INVALID_REQUEST = "invalid_request";
|
||||
/** Client authentication failed (e.g., unknown client, no client authentication included, or unsupported
|
||||
authentication method). **/
|
||||
protected static final String ERROR_INVALID_CLIENT = "invalid_client";
|
||||
/** The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is
|
||||
invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to
|
||||
another client. **/
|
||||
protected static final String ERROR_INVALID_GRANT = "invalid_grant";
|
||||
/** The authenticated client is not authorized to use this authorization grant type. **/
|
||||
protected static final String ERROR_UNAUTHORIZED_CLIENT = "unauthorized_client";
|
||||
/** The authorization grant type is not supported by the authorization server. **/
|
||||
protected static final String ERROR_UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
|
||||
/** The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. **/
|
||||
protected static final String ERROR_INVALID_SCOPE = "invalid_scope";
|
||||
|
||||
protected final String ACCESS_TOKEN = "SUPER-SECURE-TOKEN";
|
||||
protected final String REFRESH_ACCESS_TOKEN = "SUPER-SECURE-REFRESH-TOKEN";
|
||||
|
||||
|
||||
private String clientId;
|
||||
|
||||
|
||||
public OAuth2TokenPage(SmartHomeImpl smartHome) {}
|
||||
public OAuth2TokenPage(SmartHomeImpl smartHome, String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DataNode jsonRespond(
|
||||
HttpPrintStream out,
|
||||
HttpHeader headers,
|
||||
Map<String, Object> session,
|
||||
Map<String, String> cookie,
|
||||
Map<String, String> request) {
|
||||
|
||||
// POST
|
||||
|
||||
out.setHeader("Access-Control-Allow-Origin", "*");
|
||||
out.setHeader("Cache-Control", "no-store");
|
||||
out.setHeader("Pragma", "no-cache");
|
||||
|
||||
DataNode jsonRes = new DataNode(DataNode.DataType.Map);
|
||||
|
||||
if (!request.containsKey("client_id"))
|
||||
return errorResponse(out, ERROR_INVALID_REQUEST , request.get("state"), "Missing mandatory parameter client_id.");
|
||||
|
||||
if (clientId != null && clientId.equals(request.containsKey("client_id")))
|
||||
return errorResponse(out, ERROR_INVALID_CLIENT , request.get("state"), "Invalid client_id provided.");
|
||||
|
||||
if (!OAuth2AuthPage.SECRET_CODE.equals(request.get("code")))
|
||||
return errorResponse(out, ERROR_INVALID_GRANT, request.get("state"), "Invalid code value provided.");
|
||||
|
||||
String grantType = request.get("grant_type");
|
||||
|
||||
switch (grantType) {
|
||||
case "authorization_code":
|
||||
jsonRes.set("refresh_token", REFRESH_ACCESS_TOKEN);
|
||||
break;
|
||||
default:
|
||||
return errorResponse(out, ERROR_UNSUPPORTED_GRANT_TYPE, request.get("state"), "Unsupported grant_type: " + request.containsKey("grant_type"));
|
||||
}
|
||||
|
||||
jsonRes.set("access_token", ACCESS_TOKEN);
|
||||
jsonRes.set("token_type", "bearer");
|
||||
jsonRes.set("expires_in", SECONDS_IN_DAY);
|
||||
//jsonRes.set("scope", SECONDS_IN_DAY);
|
||||
if (request.containsKey("state")) jsonRes.set("state", request.get("state"));
|
||||
|
||||
return jsonRes;
|
||||
}
|
||||
|
||||
private static DataNode errorResponse(HttpPrintStream out, String error, String state, String description) {
|
||||
out.setResponseStatusCode(400);
|
||||
|
||||
DataNode jsonErr = new DataNode(DataNode.DataType.Map);
|
||||
jsonErr.set("error", error);
|
||||
if (description != null) jsonErr.set("error_description", description);
|
||||
//if (uri != null) jsonErr.set("error_uri", uri);
|
||||
if (state != null) jsonErr.set("state", state);
|
||||
|
||||
return jsonErr;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue