Make OAuth pages to follow spec
This commit is contained in:
parent
e0c464416d
commit
baaeddb9fb
3 changed files with 136 additions and 30 deletions
|
|
@ -62,7 +62,8 @@ public class SmartHomeDaemon implements HalDaemon {
|
|||
);
|
||||
|
||||
httpServer = new HttpServer(HalContext.getIntegerProperty(PARAM_PORT));
|
||||
httpServer.setPage(OAuth2AuthPage.ENDPOINT_URL, new OAuth2AuthPage(smartHome));
|
||||
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(SmartHomePage.ENDPOINT_URL, new SmartHomePage(smartHome));
|
||||
httpServer.start();
|
||||
|
|
|
|||
|
|
@ -54,14 +54,47 @@ 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
|
||||
|
|
@ -73,29 +106,58 @@ public class OAuth2AuthPage implements HttpPage {
|
|||
Map<String, String> request) throws MalformedURLException {
|
||||
|
||||
if (!request.containsKey("redirect_uri")) {
|
||||
out.setResponseStatusCode(400);
|
||||
out.println("400: Bad Request, missing property: 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())) {
|
||||
out.setResponseStatusCode(400);
|
||||
out.println("Bad redirect protocol: " + url.getProtocol());
|
||||
errorResponse(out, "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;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,15 +51,42 @@ 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
|
||||
|
|
@ -73,32 +100,48 @@ public class OAuth2TokenPage extends HttpJsonPage {
|
|||
// 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 (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 (!request.containsKey("client_id"))
|
||||
return errorResponse(out, ERROR_INVALID_REQUEST , request.get("state"), "Missing mandatory parameter client_id.");
|
||||
|
||||
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;
|
||||
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