[OAuth] Added capability for custom deserializer (#1891)

* Added capability for custom deserializer

Closes #1888

Signed-off-by: clinique <gael@lhopital.org>
pull/1995/head
Gaël L'hopital 2020-12-23 15:40:54 +01:00 committed by GitHub
parent 19daef5d09
commit f7e03397fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 9 deletions

View File

@ -35,6 +35,8 @@ import org.openhab.core.io.net.http.HttpClientFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.JsonDeserializer;
/** /**
* Implementation of OAuthClientService. * Implementation of OAuthClientService.
* *
@ -49,6 +51,7 @@ import org.slf4j.LoggerFactory;
* *
* @author Michael Bock - Initial contribution * @author Michael Bock - Initial contribution
* @author Gary Tse - Initial contribution * @author Gary Tse - Initial contribution
* @author Gaël L'hopital - Added capability for custom deserializer
*/ */
@NonNullByDefault @NonNullByDefault
public class OAuthClientServiceImpl implements OAuthClientService { public class OAuthClientServiceImpl implements OAuthClientService {
@ -150,7 +153,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("Missing client ID"); throw new OAuthException("Missing client ID");
} }
OAuthConnector connector = new OAuthConnector(httpClientFactory); OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
return connector.getAuthorizationUrl(authorizationUrl, clientId, redirectURI, persistedParams.state, return connector.getAuthorizationUrl(authorizationUrl, clientId, redirectURI, persistedParams.state,
scopeToUse); scopeToUse);
} }
@ -204,7 +207,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("Missing client ID"); throw new OAuthException("Missing client ID");
} }
OAuthConnector connector = new OAuthConnector(httpClientFactory); OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode, AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode,
clientId, persistedParams.clientSecret, redirectURI, clientId, persistedParams.clientSecret, redirectURI,
Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -236,7 +239,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("Missing token url"); throw new OAuthException("Missing token url");
} }
OAuthConnector connector = new OAuthConnector(httpClientFactory); OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
AccessTokenResponse accessTokenResponse = connector.grantTypePassword(tokenUrl, username, password, AccessTokenResponse accessTokenResponse = connector.grantTypePassword(tokenUrl, username, password,
persistedParams.clientId, persistedParams.clientSecret, scope, persistedParams.clientId, persistedParams.clientSecret, scope,
Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -261,7 +264,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("Missing client ID"); throw new OAuthException("Missing client ID");
} }
OAuthConnector connector = new OAuthConnector(httpClientFactory); OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
// depending on usage, cannot guarantee every parameter is not null at the beginning // depending on usage, cannot guarantee every parameter is not null at the beginning
AccessTokenResponse accessTokenResponse = connector.grantTypeClientCredentials(tokenUrl, clientId, AccessTokenResponse accessTokenResponse = connector.grantTypeClientCredentials(tokenUrl, clientId,
persistedParams.clientSecret, scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); persistedParams.clientSecret, scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -295,7 +298,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("tokenUrl is required but null"); throw new OAuthException("tokenUrl is required but null");
} }
OAuthConnector connector = new OAuthConnector(httpClientFactory); OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl, AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl,
lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret, lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret,
persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth)); persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -395,4 +398,17 @@ public class OAuthClientServiceImpl implements OAuthClientService {
private String createNewState() { private String createNewState() {
return UUID.randomUUID().toString(); return UUID.randomUUID().toString();
} }
@Override
public <T extends JsonDeserializer<?>> OAuthClientService withDeserializer(Class<T> deserializerClass) {
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, persistedParams.tokenExpiresInSeconds,
httpClientFactory);
persistedParams.deserializerClassName = deserializerClass.getName();
clientService.persistedParams = persistedParams;
clientService.storeHandler = storeHandler;
storeHandler.savePersistedParams(handle, clientService.persistedParams);
return clientService;
}
} }

View File

@ -15,6 +15,7 @@ package org.openhab.core.auth.oauth2client.internal;
import static org.openhab.core.auth.oauth2client.internal.Keyword.*; import static org.openhab.core.auth.oauth2client.internal.Keyword.*;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.AccessController; import java.security.AccessController;
@ -65,9 +66,21 @@ public class OAuthConnector {
private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class); private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class);
private final Gson gson; private final Gson gson;
public OAuthConnector(HttpClientFactory httpClientFactory) { public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable String deserializerClassName) {
this.httpClientFactory = httpClientFactory; this.httpClientFactory = httpClientFactory;
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); GsonBuilder gsonBuilder = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
if (deserializerClassName != null) {
try {
Class<?> deserializerClass = Class.forName(deserializerClassName);
gsonBuilder = gsonBuilder.registerTypeAdapter(AccessTokenResponse.class,
deserializerClass.getConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException
| ClassNotFoundException e) {
logger.error("Unable to construct custom deserializer '{}'", deserializerClassName, e);
}
}
gson = gsonBuilder.create();
} }
/** /**

View File

@ -70,7 +70,7 @@ public class OAuthFactoryImpl implements OAuthFactory {
@Nullable Boolean supportsBasicAuth) { @Nullable Boolean supportsBasicAuth) {
PersistedParams params = oAuthStoreHandler.loadPersistedParams(handle); PersistedParams params = oAuthStoreHandler.loadPersistedParams(handle);
PersistedParams newParams = new PersistedParams(handle, tokenUrl, authorizationUrl, clientId, clientSecret, PersistedParams newParams = new PersistedParams(handle, tokenUrl, authorizationUrl, clientId, clientSecret,
scope, supportsBasicAuth, tokenExpiresInBuffer); scope, supportsBasicAuth, tokenExpiresInBuffer, null);
OAuthClientService clientImpl = null; OAuthClientService clientImpl = null;
// If parameters in storage and parameters are the same as arguments passed get the client from storage // If parameters in storage and parameters are the same as arguments passed get the client from storage

View File

@ -20,6 +20,7 @@ import org.eclipse.jdt.annotation.Nullable;
* @author Michael Bock - Initial contribution * @author Michael Bock - Initial contribution
* @author Gary Tse - Initial contribution * @author Gary Tse - Initial contribution
* @author Hilbrand Bouwkamp - Moved class to it's own file and added hashCode and equals methods * @author Hilbrand Bouwkamp - Moved class to it's own file and added hashCode and equals methods
* @author Gaël L'hopital - Added deserializerClassName
*/ */
class PersistedParams { class PersistedParams {
String handle; String handle;
@ -32,6 +33,8 @@ class PersistedParams {
String state; String state;
String redirectUri; String redirectUri;
int tokenExpiresInSeconds = 60; int tokenExpiresInSeconds = 60;
@Nullable
String deserializerClassName;
/** /**
* Default constructor needed for json serialization. * Default constructor needed for json serialization.
@ -56,9 +59,11 @@ class PersistedParams {
* of the access tokens. This allows the access token to expire earlier than the * of the access tokens. This allows the access token to expire earlier than the
* official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke, * official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke,
* only to find the token immediately expired. * only to find the token immediately expired.
* @param deserializerClass (optional) if a specific deserializer is needed
*/ */
public PersistedParams(String handle, String tokenUrl, String authorizationUrl, String clientId, public PersistedParams(String handle, String tokenUrl, String authorizationUrl, String clientId,
String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds) { String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds,
@Nullable String deserializerClassName) {
this.handle = handle; this.handle = handle;
this.tokenUrl = tokenUrl; this.tokenUrl = tokenUrl;
this.authorizationUrl = authorizationUrl; this.authorizationUrl = authorizationUrl;
@ -67,6 +72,7 @@ class PersistedParams {
this.scope = scope; this.scope = scope;
this.supportsBasicAuth = supportsBasicAuth; this.supportsBasicAuth = supportsBasicAuth;
this.tokenExpiresInSeconds = tokenExpiresInSeconds; this.tokenExpiresInSeconds = tokenExpiresInSeconds;
this.deserializerClassName = deserializerClassName;
} }
@Override @Override
@ -83,6 +89,7 @@ class PersistedParams {
result = prime * result + ((supportsBasicAuth == null) ? 0 : supportsBasicAuth.hashCode()); result = prime * result + ((supportsBasicAuth == null) ? 0 : supportsBasicAuth.hashCode());
result = prime * result + tokenExpiresInSeconds; result = prime * result + tokenExpiresInSeconds;
result = prime * result + ((tokenUrl == null) ? 0 : tokenUrl.hashCode()); result = prime * result + ((tokenUrl == null) ? 0 : tokenUrl.hashCode());
result = prime * result + ((deserializerClassName != null) ? deserializerClassName.hashCode() : 0);
return result; return result;
} }
@ -161,6 +168,14 @@ class PersistedParams {
} else if (!tokenUrl.equals(other.tokenUrl)) { } else if (!tokenUrl.equals(other.tokenUrl)) {
return false; return false;
} }
if (deserializerClassName == null) {
if (other.deserializerClassName != null) {
return false;
}
} else if (deserializerClassName != null && !deserializerClassName.equals(other.deserializerClassName)) {
return false;
}
return true; return true;
} }
} }

View File

@ -17,6 +17,8 @@ import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializer;
/** /**
* This is the service factory to produce a OAuth2 service client that authenticates using OAUTH2. * This is the service factory to produce a OAuth2 service client that authenticates using OAUTH2.
* This is a service factory pattern; the OAuthe2 service client is not shared between bundles. * This is a service factory pattern; the OAuthe2 service client is not shared between bundles.
@ -77,6 +79,7 @@ import org.eclipse.jdt.annotation.Nullable;
* *
* @author Gary Tse - Initial contribution * @author Gary Tse - Initial contribution
* @author Hilbrand Bouwkamp - Added AccessTokenRefreshListener, fixed javadoc warnings * @author Hilbrand Bouwkamp - Added AccessTokenRefreshListener, fixed javadoc warnings
* @author Gaël L'hopital - Added capability for custom deserializer
*/ */
@NonNullByDefault @NonNullByDefault
public interface OAuthClientService extends AutoCloseable { public interface OAuthClientService extends AutoCloseable {
@ -286,4 +289,12 @@ public interface OAuthClientService extends AutoCloseable {
* @param listener the listener to remove * @param listener the listener to remove
*/ */
boolean removeAccessTokenRefreshListener(AccessTokenRefreshListener listener); boolean removeAccessTokenRefreshListener(AccessTokenRefreshListener listener);
/**
* Adds a personalized deserializer to a given oauth service.
*
* @param deserializeClass the deserializer class that should be used to deserialize AccessTokenResponse
* @return the oauth service
*/
<T extends JsonDeserializer<?>> OAuthClientService withDeserializer(Class<T> deserializerClass);
} }