mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
[oauth] Add support for custom deserialization of AccessTokenResponse (#3537)
* Add possibility to inject custom GsonBuilder Reverts #1891 Fixes #1888 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
+25
-13
@@ -35,7 +35,7 @@ import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Implementation of OAuthClientService.
|
||||
@@ -68,16 +68,19 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
private final String handle;
|
||||
private final int tokenExpiresInSeconds;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final @Nullable GsonBuilder gsonBuilder;
|
||||
private final List<AccessTokenRefreshListener> accessTokenRefreshListeners = new ArrayList<>();
|
||||
|
||||
private PersistedParams persistedParams = new PersistedParams();
|
||||
|
||||
private volatile boolean closed = false;
|
||||
|
||||
private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpClientFactory httpClientFactory) {
|
||||
private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpClientFactory httpClientFactory,
|
||||
@Nullable GsonBuilder gsonBuilder) {
|
||||
this.handle = handle;
|
||||
this.tokenExpiresInSeconds = tokenExpiresInSeconds;
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.gsonBuilder = gsonBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +106,7 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
return null;
|
||||
}
|
||||
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, tokenExpiresInSeconds,
|
||||
httpClientFactory);
|
||||
httpClientFactory, null);
|
||||
clientService.storeHandler = storeHandler;
|
||||
clientService.persistedParams = persistedParamsFromStore;
|
||||
|
||||
@@ -118,13 +121,13 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
* {@link org.openhab.core.auth.client.oauth2.OAuthFactory#createOAuthClientService}*
|
||||
* @param storeHandler Storage handler
|
||||
* @param httpClientFactory Http client factory
|
||||
* @param persistedParams These parameters are static with respect to the oauth provider and thus can be persisted.
|
||||
* @param persistedParams These parameters are static with respect to the OAuth provider and thus can be persisted.
|
||||
* @return OAuthClientServiceImpl an instance
|
||||
*/
|
||||
static OAuthClientServiceImpl createInstance(String handle, OAuthStoreHandler storeHandler,
|
||||
HttpClientFactory httpClientFactory, PersistedParams params) {
|
||||
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, params.tokenExpiresInSeconds,
|
||||
httpClientFactory);
|
||||
httpClientFactory, null);
|
||||
|
||||
clientService.storeHandler = storeHandler;
|
||||
clientService.persistedParams = params;
|
||||
@@ -153,7 +156,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
|
||||
GsonBuilder gsonBuilder = this.gsonBuilder;
|
||||
OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory)
|
||||
: new OAuthConnector(httpClientFactory, gsonBuilder);
|
||||
return connector.getAuthorizationUrl(authorizationUrl, clientId, redirectURI, persistedParams.state,
|
||||
scopeToUse);
|
||||
}
|
||||
@@ -207,7 +212,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
|
||||
GsonBuilder gsonBuilder = this.gsonBuilder;
|
||||
OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory)
|
||||
: new OAuthConnector(httpClientFactory, gsonBuilder);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode,
|
||||
clientId, persistedParams.clientSecret, redirectURI,
|
||||
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
@@ -239,7 +246,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
throw new OAuthException("Missing token url");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
|
||||
GsonBuilder gsonBuilder = this.gsonBuilder;
|
||||
OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory)
|
||||
: new OAuthConnector(httpClientFactory, gsonBuilder);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypePassword(tokenUrl, username, password,
|
||||
persistedParams.clientId, persistedParams.clientSecret, scope,
|
||||
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
@@ -264,7 +273,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
|
||||
GsonBuilder gsonBuilder = this.gsonBuilder;
|
||||
OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory)
|
||||
: new OAuthConnector(httpClientFactory, gsonBuilder);
|
||||
// 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));
|
||||
@@ -298,7 +309,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
throw new OAuthException("tokenUrl is required but null");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory, persistedParams.deserializerClassName);
|
||||
GsonBuilder gsonBuilder = this.gsonBuilder;
|
||||
OAuthConnector connector = gsonBuilder == null ? new OAuthConnector(httpClientFactory)
|
||||
: new OAuthConnector(httpClientFactory, gsonBuilder);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl,
|
||||
lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret,
|
||||
persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
@@ -400,10 +413,9 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends JsonDeserializer<?>> OAuthClientService withDeserializer(Class<T> deserializerClass) {
|
||||
public OAuthClientService withGsonBuilder(GsonBuilder gsonBuilder) {
|
||||
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, persistedParams.tokenExpiresInSeconds,
|
||||
httpClientFactory);
|
||||
persistedParams.deserializerClassName = deserializerClass.getName();
|
||||
httpClientFactory, gsonBuilder);
|
||||
clientService.persistedParams = persistedParams;
|
||||
clientService.storeHandler = storeHandler;
|
||||
|
||||
|
||||
+7
-17
@@ -15,7 +15,6 @@ 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.time.Instant;
|
||||
@@ -67,29 +66,20 @@ public class OAuthConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class);
|
||||
private final Gson gson;
|
||||
|
||||
public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable String deserializerClassName) {
|
||||
public OAuthConnector(HttpClientFactory httpClientFactory) {
|
||||
this(httpClientFactory, new GsonBuilder());
|
||||
}
|
||||
|
||||
public OAuthConnector(HttpClientFactory httpClientFactory, GsonBuilder gsonBuilder) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
GsonBuilder gsonBuilder = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
gson = gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (json, typeOfT, context) -> {
|
||||
try {
|
||||
return Instant.parse(json.getAsString());
|
||||
} catch (DateTimeParseException e) {
|
||||
return LocalDateTime.parse(json.getAsString()).atZone(ZoneId.systemDefault()).toInstant();
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}).create();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-12
@@ -33,8 +33,6 @@ class PersistedParams {
|
||||
String state;
|
||||
String redirectUri;
|
||||
int tokenExpiresInSeconds = 60;
|
||||
@Nullable
|
||||
String deserializerClassName;
|
||||
|
||||
/**
|
||||
* Default constructor needed for json serialization.
|
||||
@@ -59,7 +57,6 @@ 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,
|
||||
@@ -72,7 +69,6 @@ class PersistedParams {
|
||||
this.scope = scope;
|
||||
this.supportsBasicAuth = supportsBasicAuth;
|
||||
this.tokenExpiresInSeconds = tokenExpiresInSeconds;
|
||||
this.deserializerClassName = deserializerClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +85,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -168,13 +163,6 @@ 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;
|
||||
}
|
||||
|
||||
+6
-6
@@ -17,11 +17,11 @@ import java.io.IOException;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* This is the service factory to produce an 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 OAuth2 service client is not shared between bundles.
|
||||
*
|
||||
* <p>
|
||||
* The basic uses of this OAuthClient are as follows:
|
||||
@@ -291,10 +291,10 @@ public interface OAuthClientService extends AutoCloseable {
|
||||
boolean removeAccessTokenRefreshListener(AccessTokenRefreshListener listener);
|
||||
|
||||
/**
|
||||
* Adds a personalized deserializer to a given oauth service.
|
||||
* Adds a custom GsonBuilder to be used with the OAuth service instance.
|
||||
*
|
||||
* @param deserializeClass the deserializer class that should be used to deserialize AccessTokenResponse
|
||||
* @return the oauth service
|
||||
* @param gsonBuilder the custom GsonBuilder instance
|
||||
* @return the OAuth service
|
||||
*/
|
||||
<T extends JsonDeserializer<?>> OAuthClientService withDeserializer(Class<T> deserializerClass);
|
||||
OAuthClientService withGsonBuilder(GsonBuilder gsonBuilder);
|
||||
}
|
||||
|
||||
+11
-11
@@ -26,12 +26,12 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
public interface OAuthFactory {
|
||||
|
||||
/**
|
||||
* Creates a new oauth service. Use this method only once to obtain a handle and store
|
||||
* Creates a new OAuth service. Use this method only once to obtain a handle and store
|
||||
* this handle for further in a persistent storage container.
|
||||
*
|
||||
* @param handle the handle to the oauth service
|
||||
* @param tokenUrl the token url of the oauth provider. This is used for getting access token.
|
||||
* @param authorizationUrl the authorization url of the oauth provider. This is used purely for generating
|
||||
* @param handle the handle to the OAuth service
|
||||
* @param tokenUrl the token url of the OAuth provider. This is used for getting access token.
|
||||
* @param authorizationUrl the authorization url of the OAuth provider. This is used purely for generating
|
||||
* authorization code/ url.
|
||||
* @param clientId the client id
|
||||
* @param clientSecret the client secret (optional)
|
||||
@@ -39,23 +39,23 @@ public interface OAuthFactory {
|
||||
* @param supportsBasicAuth whether the OAuth provider supports basic authorization or the client id and client
|
||||
* secret should be passed as form params. true - use http basic authentication, false - do not use http
|
||||
* basic authentication, null - unknown (default to do not use)
|
||||
* @return the oauth service
|
||||
* @return the OAuth service
|
||||
*/
|
||||
OAuthClientService createOAuthClientService(String handle, String tokenUrl, @Nullable String authorizationUrl,
|
||||
String clientId, @Nullable String clientSecret, @Nullable String scope,
|
||||
@Nullable Boolean supportsBasicAuth);
|
||||
|
||||
/**
|
||||
* Gets the oauth service for a given handle
|
||||
* Gets the OAuth service for a given handle
|
||||
*
|
||||
* @param handle the handle to the oauth service
|
||||
* @return the oauth service or null if it doesn't exist
|
||||
* @param handle the handle to the OAuth service
|
||||
* @return the OAuth service or null if it doesn't exist
|
||||
*/
|
||||
@Nullable
|
||||
OAuthClientService getOAuthClientService(String handle);
|
||||
|
||||
/**
|
||||
* Unget an oauth service, this unget/unregister the service, and frees the resources.
|
||||
* Unget an OAuth service, this unget/unregister the service, and frees the resources.
|
||||
* The existing tokens/ configurations (persisted parameters) are still saved
|
||||
* in the store. It will internally call {@code OAuthClientService#close()}.
|
||||
*
|
||||
@@ -64,7 +64,7 @@ public interface OAuthFactory {
|
||||
* If OAuth service is closed directly, without using {@code #ungetOAuthService(String)},
|
||||
* then a small residual footprint is left in the cache.
|
||||
*
|
||||
* @param handle the handle to the oauth service
|
||||
* @param handle the handle to the OAuth service
|
||||
*/
|
||||
void ungetOAuthService(String handle);
|
||||
|
||||
@@ -72,7 +72,7 @@ public interface OAuthFactory {
|
||||
* This method is for unget/unregister the service,
|
||||
* then <strong>DELETE</strong> access token, configuration data from the store
|
||||
*
|
||||
* @param handle
|
||||
* @param handle the handle to the OAuth service
|
||||
*/
|
||||
void deleteServiceAndAccessToken(String handle);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user