mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
Add extra fields support to oAuth AccessTokenResponse (#5355)
* add extra fields support to oAuth AccessTokenResponse Signed-off-by: Laurent ARNAL <laurent@clae.net>
This commit is contained in:
+6
-3
@@ -458,10 +458,13 @@ public class OAuthClientServiceImpl implements OAuthClientService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addExtraAuthField(String key, String value) {
|
public void addExtraAuthField(String key, String value) {
|
||||||
if (extraAuthFields == null) {
|
Fields lcExtraAuthFields = extraAuthFields;
|
||||||
extraAuthFields = new Fields();
|
if (lcExtraAuthFields == null) {
|
||||||
|
lcExtraAuthFields = new Fields();
|
||||||
|
extraAuthFields = lcExtraAuthFields;
|
||||||
}
|
}
|
||||||
extraAuthFields.add(key, value);
|
|
||||||
|
lcExtraAuthFields.add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+9
-4
@@ -37,6 +37,7 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.util.Fields;
|
import org.eclipse.jetty.util.Fields;
|
||||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||||
|
import org.openhab.core.auth.client.oauth2.AccessTokenResponseExtraFieldsAdapterFactory;
|
||||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||||
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
@@ -89,7 +90,11 @@ public class OAuthConnector {
|
|||||||
public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable Fields extraFields, GsonBuilder gsonBuilder) {
|
public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable Fields extraFields, GsonBuilder gsonBuilder) {
|
||||||
this.httpClientFactory = httpClientFactory;
|
this.httpClientFactory = httpClientFactory;
|
||||||
this.extraFields = extraFields;
|
this.extraFields = extraFields;
|
||||||
gson = gsonBuilder.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)
|
this.gson = getGson(gsonBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Gson getGson(GsonBuilder gsonBuilder) {
|
||||||
|
return gsonBuilder.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)
|
||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.registerTypeAdapter(OAuthResponseException.class,
|
.registerTypeAdapter(OAuthResponseException.class,
|
||||||
(JsonDeserializer<OAuthResponseException>) (json, typeOfT, context) -> {
|
(JsonDeserializer<OAuthResponseException>) (json, typeOfT, context) -> {
|
||||||
@@ -120,7 +125,7 @@ public class OAuthConnector {
|
|||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
return LocalDateTime.parse(json.getAsString()).atZone(ZoneId.systemDefault()).toInstant();
|
return LocalDateTime.parse(json.getAsString()).atZone(ZoneId.systemDefault()).toInstant();
|
||||||
}
|
}
|
||||||
}).create();
|
}).registerTypeAdapterFactory(new AccessTokenResponseExtraFieldsAdapterFactory()).create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,8 +219,8 @@ public class OAuthConnector {
|
|||||||
* @throws OAuthResponseException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
* @throws OAuthResponseException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
||||||
* Response
|
* Response
|
||||||
*/
|
*/
|
||||||
public AccessTokenResponse grantTypeRefreshToken(String tokenUrl, String refreshToken, @Nullable String clientId,
|
public AccessTokenResponse grantTypeRefreshToken(String tokenUrl, @Nullable String refreshToken,
|
||||||
@Nullable String clientSecret, @Nullable String scope, boolean supportsBasicAuth)
|
@Nullable String clientId, @Nullable String clientSecret, @Nullable String scope, boolean supportsBasicAuth)
|
||||||
throws OAuthResponseException, OAuthException, IOException {
|
throws OAuthResponseException, OAuthException, IOException {
|
||||||
HttpClient httpClient = null;
|
HttpClient httpClient = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
+4
-1
@@ -252,7 +252,10 @@ public class OAuthStoreHandlerImpl implements OAuthStoreHandler {
|
|||||||
return dcrDecrypted;
|
return dcrDecrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable String encrypt(String token) throws GeneralSecurityException {
|
private @Nullable String encrypt(@Nullable String token) throws GeneralSecurityException {
|
||||||
|
if (token == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (storageCipher.isEmpty()) {
|
if (storageCipher.isEmpty()) {
|
||||||
return token; // do nothing if no cipher
|
return token; // do nothing if no cipher
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.auth.oauth2client.internal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit tests for {@link AccessTokenResponse}
|
||||||
|
*
|
||||||
|
* @author Laurent Arnal - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
class AccessTokenResponseExtraFieldTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtraFieldDeserialization() {
|
||||||
|
Gson gson = OAuthConnector.getGson(new GsonBuilder());
|
||||||
|
|
||||||
|
// \"created_on\":\"2026-02-26T15:10:49.965249200Z\"
|
||||||
|
String json = "{\"access_token\":\"AccessToken\",\"expires_in\":60,\"refresh_token\":\"RefreshToken\",\"app_client_id\":\"ApplicationClientId\"}";
|
||||||
|
AccessTokenResponse atr = gson.fromJson(json, AccessTokenResponse.class);
|
||||||
|
|
||||||
|
if (atr != null) {
|
||||||
|
assertEquals("AccessToken", atr.getAccessToken());
|
||||||
|
assertEquals(60, atr.getExpiresIn());
|
||||||
|
assertEquals("RefreshToken", atr.getRefreshToken());
|
||||||
|
|
||||||
|
Map<String, String> extraFields = atr.getExtraFields();
|
||||||
|
|
||||||
|
assertEquals(1, extraFields.size());
|
||||||
|
assertTrue(extraFields.containsKey("app_client_id"));
|
||||||
|
assertEquals("ApplicationClientId", extraFields.get("app_client_id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-18
@@ -15,8 +15,13 @@ package org.openhab.core.auth.client.oauth2;
|
|||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the Access Token Response, a simple value-object that holds the result of the
|
* This is the Access Token Response, a simple value-object that holds the result of the
|
||||||
* from an Access Token Request, as listed in RFC 6749:
|
* from an Access Token Request, as listed in RFC 6749:
|
||||||
@@ -28,6 +33,7 @@ import java.util.Objects;
|
|||||||
* @author Michael Bock - Initial contribution
|
* @author Michael Bock - Initial contribution
|
||||||
* @author Gary Tse - Adaptation for Eclipse SmartHome
|
* @author Gary Tse - Adaptation for Eclipse SmartHome
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public final class AccessTokenResponse implements Serializable, Cloneable {
|
public final class AccessTokenResponse implements Serializable, Cloneable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,14 +52,14 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-1.4">rfc6749 section-1.4</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-1.4">rfc6749 section-1.4</a>
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-10.3">rfc6749 section-10.3</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-10.3">rfc6749 section-10.3</a>
|
||||||
*/
|
*/
|
||||||
private String accessToken;
|
private @Nullable String accessToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token type. e.g. Bearer, MAC
|
* Token type. e.g. Bearer, MAC
|
||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-7.1">rfc6749 section-7.1</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-7.1">rfc6749 section-7.1</a>
|
||||||
*/
|
*/
|
||||||
private String tokenType;
|
private @Nullable String tokenType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of seconds that this OAuthToken is valid for since the time it was created.
|
* Number of seconds that this OAuthToken is valid for since the time it was created.
|
||||||
@@ -74,7 +80,7 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-1.5">rfc6749 section-1.5</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-1.5">rfc6749 section-1.5</a>
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-10.4">rfc6749 section-10.4</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-10.4">rfc6749 section-10.4</a>
|
||||||
*/
|
*/
|
||||||
private String refreshToken;
|
private @Nullable String refreshToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A space-delimited case-sensitive un-ordered string. This may be used
|
* A space-delimited case-sensitive un-ordered string. This may be used
|
||||||
@@ -83,7 +89,7 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.3">rfc6749 section-3.3</a>
|
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.3">rfc6749 section-3.3</a>
|
||||||
*/
|
*/
|
||||||
private String scope;
|
private @Nullable String scope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the {@code state} parameter was present in the access token request.
|
* If the {@code state} parameter was present in the access token request.
|
||||||
@@ -91,7 +97,7 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
*
|
*
|
||||||
* <a href="https://tools.ietf.org/html/rfc6749#section-4.2.2">rfc6749 section-4.2.2</a>
|
* <a href="https://tools.ietf.org/html/rfc6749#section-4.2.2">rfc6749 section-4.2.2</a>
|
||||||
*/
|
*/
|
||||||
private String state;
|
private @Nullable String state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created datetime of this access token. This is generated locally
|
* Created datetime of this access token. This is generated locally
|
||||||
@@ -100,7 +106,37 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
* This should be slightly later than the actual time the access token
|
* This should be slightly later than the actual time the access token
|
||||||
* is produced at the server.
|
* is produced at the server.
|
||||||
*/
|
*/
|
||||||
private Instant createdOn;
|
private @Nullable Instant createdOn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra elements that may be passed in the token response by a specific OAuth implementation.
|
||||||
|
* <p>
|
||||||
|
* These fields are provider-specific and are not restricted to standard OAuth fields. As such,
|
||||||
|
* they may contain sensitive information (for example, identifiers, metadata or other values
|
||||||
|
* that should not be logged or exposed).
|
||||||
|
* <p>
|
||||||
|
* Consumers of this value MUST treat all entries as potentially sensitive and avoid logging or
|
||||||
|
* otherwise exposing them unless they have explicitly verified that the data is safe to do so.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Map<String, String> extraFields = Collections.emptyMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the additional provider-specific fields from the token response.
|
||||||
|
* <p>
|
||||||
|
* Note: the returned map may contain sensitive information in non-standard fields. Callers
|
||||||
|
* MUST take care not to log, persist, or expose these values without first ensuring that
|
||||||
|
* doing so is appropriate in their security context.
|
||||||
|
*
|
||||||
|
* @return a map of additional fields as provided by the authorization server
|
||||||
|
*/
|
||||||
|
public Map<String, String> getExtraFields() {
|
||||||
|
return Collections.unmodifiableMap(extraFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtraFields(Map<String, String> extraFields) {
|
||||||
|
this.extraFields = (extraFields.isEmpty()) ? Collections.emptyMap() : Map.copyOf(extraFields);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate if the token is expired against the given time.
|
* Calculate if the token is expired against the given time.
|
||||||
@@ -117,15 +153,15 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
|| createdOn.plusSeconds(expiresIn).minusSeconds(tokenExpiresInBuffer).isBefore(givenTime);
|
|| createdOn.plusSeconds(expiresIn).minusSeconds(tokenExpiresInBuffer).isBefore(givenTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessToken() {
|
public @Nullable String getAccessToken() {
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccessToken(String accessToken) {
|
public void setAccessToken(@Nullable String accessToken) {
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTokenType() {
|
public @Nullable String getTokenType() {
|
||||||
return tokenType;
|
return tokenType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,15 +177,15 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
this.expiresIn = expiresIn;
|
this.expiresIn = expiresIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRefreshToken() {
|
public @Nullable String getRefreshToken() {
|
||||||
return refreshToken;
|
return refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRefreshToken(String refreshToken) {
|
public void setRefreshToken(@Nullable String refreshToken) {
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getScope() {
|
public @Nullable String getScope() {
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +193,7 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getState() {
|
public @Nullable String getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +201,7 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getCreatedOn() {
|
public @Nullable Instant getCreatedOn() {
|
||||||
return createdOn;
|
return createdOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,11 +220,11 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(accessToken, tokenType, expiresIn, refreshToken, scope, state, createdOn);
|
return Objects.hash(accessToken, tokenType, expiresIn, refreshToken, scope, state, createdOn, extraFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object thatAuthTokenObj) {
|
public boolean equals(@Nullable Object thatAuthTokenObj) {
|
||||||
if (this == thatAuthTokenObj) {
|
if (this == thatAuthTokenObj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -203,13 +239,18 @@ public final class AccessTokenResponse implements Serializable, Cloneable {
|
|||||||
return Objects.equals(this.accessToken, that.accessToken) && Objects.equals(this.tokenType, that.tokenType)
|
return Objects.equals(this.accessToken, that.accessToken) && Objects.equals(this.tokenType, that.tokenType)
|
||||||
&& Objects.equals(this.expiresIn, that.expiresIn)
|
&& Objects.equals(this.expiresIn, that.expiresIn)
|
||||||
&& Objects.equals(this.refreshToken, that.refreshToken) && Objects.equals(this.scope, that.scope)
|
&& Objects.equals(this.refreshToken, that.refreshToken) && Objects.equals(this.scope, that.scope)
|
||||||
&& Objects.equals(this.state, that.state) && Objects.equals(this.createdOn, that.createdOn);
|
&& Objects.equals(this.state, that.state) && Objects.equals(this.createdOn, that.createdOn)
|
||||||
|
&& Objects.equals(this.extraFields, that.extraFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
/*
|
||||||
|
* Warning: the toString() function may returns sensitive information that should not go into the log.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AccessTokenResponse [accessToken=" + accessToken + ", tokenType=" + tokenType + ", expiresIn="
|
return "AccessTokenResponse [accessToken=" + accessToken + ", tokenType=" + tokenType + ", expiresIn="
|
||||||
+ expiresIn + ", refreshToken=" + refreshToken + ", scope=" + scope + ", state=" + state
|
+ expiresIn + ", refreshToken=" + refreshToken + ", scope=" + scope + ", state=" + state
|
||||||
+ ", createdOn=" + createdOn + "]";
|
+ ", createdOn=" + createdOn + ", extraFields= " + extraFields + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.auth.client.oauth2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link TypeAdapterFactory} that decorates the default {@link AccessTokenResponse} adapter in order to capture
|
||||||
|
* additional fields returned by an OAuth 2.0 authorization server that are not part of the standard RFC 6749
|
||||||
|
* specification. All unknown JSON properties are collected into a map and exposed via {@code extraFields} on the
|
||||||
|
* {@link AccessTokenResponse}.
|
||||||
|
*
|
||||||
|
* @author Laurent Arnal - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public final class AccessTokenResponseExtraFieldsAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
|
private static final Set<String> KNOWN_FIELDS = Set.of("access_token", "token_type", "expires_in", "refresh_token",
|
||||||
|
"scope", "state", "created_on", "extra_fields");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable TypeAdapter<T> create(@Nullable Gson gson, @Nullable TypeToken<T> type) {
|
||||||
|
if (type == null || !AccessTokenResponse.class.isAssignableFrom(type.getRawType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gson == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
|
||||||
|
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
|
||||||
|
|
||||||
|
return new TypeAdapter<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
delegate.write(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
|
JsonElement tree = elementAdapter.read(in);
|
||||||
|
if (tree == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tree.isJsonObject()) {
|
||||||
|
return delegate.fromJsonTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject obj = tree.getAsJsonObject();
|
||||||
|
|
||||||
|
T parsed = delegate.fromJsonTree(tree);
|
||||||
|
AccessTokenResponse response = (AccessTokenResponse) parsed;
|
||||||
|
|
||||||
|
Map<String, String> extras = new HashMap<>();
|
||||||
|
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (KNOWN_FIELDS.contains(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
extras.put(key, toStringValue(gson, entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null) {
|
||||||
|
response.setExtraFields(extras);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toStringValue(Gson gson, JsonElement el) {
|
||||||
|
if (el.isJsonNull()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive p = el.getAsJsonPrimitive();
|
||||||
|
return p.getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return gson.toJson(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user