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

* Added capability for custom deserializer

Closes #1888

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
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.LoggerFactory;
import com.google.gson.JsonDeserializer;
/**
* Implementation of OAuthClientService.
*
@ -49,6 +51,7 @@ import org.slf4j.LoggerFactory;
*
* @author Michael Bock - Initial contribution
* @author Gary Tse - Initial contribution
* @author Gaël L'hopital - Added capability for custom deserializer
*/
@NonNullByDefault
public class OAuthClientServiceImpl implements OAuthClientService {
@ -150,7 +153,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
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,
scopeToUse);
}
@ -204,7 +207,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
throw new OAuthException("Missing client ID");
}
OAuthConnector connector = new OAuthConnector(httpClientFactory);
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode,
clientId, persistedParams.clientSecret, redirectURI,
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -236,7 +239,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
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,
persistedParams.clientId, persistedParams.clientSecret, scope,
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -261,7 +264,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
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
AccessTokenResponse accessTokenResponse = connector.grantTypeClientCredentials(tokenUrl, clientId,
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");
}
OAuthConnector connector = new OAuthConnector(httpClientFactory);
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl,
lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret,
persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
@ -395,4 +398,17 @@ public class OAuthClientServiceImpl implements OAuthClientService {
private String createNewState() {
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 java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
@ -65,9 +66,21 @@ public class OAuthConnector {
private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class);
private final Gson gson;
public OAuthConnector(HttpClientFactory httpClientFactory) {
public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable String deserializerClassName) {
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) {
PersistedParams params = oAuthStoreHandler.loadPersistedParams(handle);
PersistedParams newParams = new PersistedParams(handle, tokenUrl, authorizationUrl, clientId, clientSecret,
scope, supportsBasicAuth, tokenExpiresInBuffer);
scope, supportsBasicAuth, tokenExpiresInBuffer, null);
OAuthClientService clientImpl = null;
// 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 Gary Tse - Initial contribution
* @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 {
String handle;
@ -32,6 +33,8 @@ class PersistedParams {
String state;
String redirectUri;
int tokenExpiresInSeconds = 60;
@Nullable
String deserializerClassName;
/**
* 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
* official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke,
* 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,
String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds) {
String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds,
@Nullable String deserializerClassName) {
this.handle = handle;
this.tokenUrl = tokenUrl;
this.authorizationUrl = authorizationUrl;
@ -67,6 +72,7 @@ class PersistedParams {
this.scope = scope;
this.supportsBasicAuth = supportsBasicAuth;
this.tokenExpiresInSeconds = tokenExpiresInSeconds;
this.deserializerClassName = deserializerClassName;
}
@Override
@ -83,6 +89,7 @@ class PersistedParams {
result = prime * result + ((supportsBasicAuth == null) ? 0 : supportsBasicAuth.hashCode());
result = prime * result + tokenExpiresInSeconds;
result = prime * result + ((tokenUrl == null) ? 0 : tokenUrl.hashCode());
result = prime * result + ((deserializerClassName != null) ? deserializerClassName.hashCode() : 0);
return result;
}
@ -161,6 +168,14 @@ class PersistedParams {
} else if (!tokenUrl.equals(other.tokenUrl)) {
return false;
}
if (deserializerClassName == null) {
if (other.deserializerClassName != null) {
return false;
}
} else if (deserializerClassName != null && !deserializerClassName.equals(other.deserializerClassName)) {
return false;
}
return true;
}
}

View File

@ -17,6 +17,8 @@ import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
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 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 Hilbrand Bouwkamp - Added AccessTokenRefreshListener, fixed javadoc warnings
* @author Gaël L'hopital - Added capability for custom deserializer
*/
@NonNullByDefault
public interface OAuthClientService extends AutoCloseable {
@ -286,4 +289,12 @@ public interface OAuthClientService extends AutoCloseable {
* @param listener the listener to remove
*/
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);
}