mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-05 10:12:36 +00:00
Add support for device authorization response
Closes gh-12852
This commit is contained in:
parent
ac1d269e73
commit
8c17b978c8
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -32,6 +32,7 @@ import org.springframework.util.Assert;
|
|||||||
* extensibility mechanism for defining additional grant types.
|
* extensibility mechanism for defining additional grant types.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Steve Riesenberg
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||||
* 1.3 Authorization Grant</a>
|
* 1.3 Authorization Grant</a>
|
||||||
@ -62,6 +63,12 @@ public final class AuthorizationGrantType implements Serializable {
|
|||||||
public static final AuthorizationGrantType JWT_BEARER = new AuthorizationGrantType(
|
public static final AuthorizationGrantType JWT_BEARER = new AuthorizationGrantType(
|
||||||
"urn:ietf:params:oauth:grant-type:jwt-bearer");
|
"urn:ietf:params:oauth:grant-type:jwt-bearer");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final AuthorizationGrantType DEVICE_CODE = new AuthorizationGrantType(
|
||||||
|
"urn:ietf:params:oauth:grant-type:device_code");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of an {@link AbstractOAuth2Token} representing a device code as part
|
||||||
|
* of the OAuth 2.0 Device Authorization Grant.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 6.1
|
||||||
|
* @see OAuth2UserCode
|
||||||
|
* @see <a target="_blank" href= "https://tools.ietf.org/html/rfc8628#section-3.2">Section
|
||||||
|
* 3.2 Device Authorization Response</a>
|
||||||
|
*/
|
||||||
|
public final class OAuth2DeviceCode extends AbstractOAuth2Token {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2DeviceCode} using the provided parameters.
|
||||||
|
* @param tokenValue the token value
|
||||||
|
* @param issuedAt the time at which the token was issued
|
||||||
|
* @param expiresAt the time at which the token expires
|
||||||
|
*/
|
||||||
|
public OAuth2DeviceCode(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||||
|
super(tokenValue, issuedAt, expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of an {@link AbstractOAuth2Token} representing a user code as part of
|
||||||
|
* the OAuth 2.0 Device Authorization Grant.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 6.1
|
||||||
|
* @see OAuth2DeviceCode
|
||||||
|
* @see <a target="_blank" href= "https://tools.ietf.org/html/rfc8628#section-3.2">Section
|
||||||
|
* 3.2 Device Authorization Response</a>
|
||||||
|
*/
|
||||||
|
public final class OAuth2UserCode extends AbstractOAuth2Token {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2UserCode} using the provided parameters.
|
||||||
|
* @param tokenValue the token value
|
||||||
|
* @param issuedAt the time at which the token was issued
|
||||||
|
* @param expiresAt the time at which the token expires
|
||||||
|
*/
|
||||||
|
public OAuth2UserCode(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||||
|
super(tokenValue, issuedAt, expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core.endpoint;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2UserCode;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of an OAuth 2.0 Device Authorization Response.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 6.1
|
||||||
|
* @see OAuth2DeviceCode
|
||||||
|
* @see OAuth2UserCode
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8628#section-3.2">Section
|
||||||
|
* 3.2 Device Authorization Response</a>
|
||||||
|
*/
|
||||||
|
public final class OAuth2DeviceAuthorizationResponse {
|
||||||
|
|
||||||
|
private OAuth2DeviceCode deviceCode;
|
||||||
|
|
||||||
|
private OAuth2UserCode userCode;
|
||||||
|
|
||||||
|
private String verificationUri;
|
||||||
|
|
||||||
|
private String verificationUriComplete;
|
||||||
|
|
||||||
|
private long interval;
|
||||||
|
|
||||||
|
private Map<String, Object> additionalParameters;
|
||||||
|
|
||||||
|
private OAuth2DeviceAuthorizationResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2DeviceCode Device Code}.
|
||||||
|
* @return the {@link OAuth2DeviceCode}
|
||||||
|
*/
|
||||||
|
public OAuth2DeviceCode getDeviceCode() {
|
||||||
|
return this.deviceCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2UserCode User Code}.
|
||||||
|
* @return the {@link OAuth2UserCode}
|
||||||
|
*/
|
||||||
|
public OAuth2UserCode getUserCode() {
|
||||||
|
return this.userCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the end-user verification URI.
|
||||||
|
* @return the end-user verification URI
|
||||||
|
*/
|
||||||
|
public String getVerificationUri() {
|
||||||
|
return this.verificationUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the end-user verification URI that includes the user code.
|
||||||
|
* @return the end-user verification URI that includes the user code
|
||||||
|
*/
|
||||||
|
public String getVerificationUriComplete() {
|
||||||
|
return this.verificationUriComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum amount of time (in seconds) that the client should wait between
|
||||||
|
* polling requests to the token endpoint.
|
||||||
|
* @return the minimum amount of time between polling requests
|
||||||
|
*/
|
||||||
|
public long getInterval() {
|
||||||
|
return this.interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the additional parameters returned in the response.
|
||||||
|
* @return a {@code Map} of the additional parameters returned in the response, may be
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getAdditionalParameters() {
|
||||||
|
return this.additionalParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}, initialized with the provided device code and user
|
||||||
|
* code values.
|
||||||
|
* @param deviceCode the value of the device code
|
||||||
|
* @param userCode the value of the user code
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder with(String deviceCode, String userCode) {
|
||||||
|
Assert.hasText(deviceCode, "deviceCode cannot be empty");
|
||||||
|
Assert.hasText(userCode, "userCode cannot be empty");
|
||||||
|
return new Builder(deviceCode, userCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}, initialized with the provided device code and user
|
||||||
|
* code.
|
||||||
|
* @param deviceCode the {@link OAuth2DeviceCode}
|
||||||
|
* @param userCode the {@link OAuth2UserCode}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder with(OAuth2DeviceCode deviceCode, OAuth2UserCode userCode) {
|
||||||
|
Assert.notNull(deviceCode, "deviceCode cannot be null");
|
||||||
|
Assert.notNull(userCode, "userCode cannot be null");
|
||||||
|
return new Builder(deviceCode, userCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}, initialized with the provided response.
|
||||||
|
* @param deviceAuthorizationResponse the response to initialize the builder with
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder withResponse(OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse) {
|
||||||
|
Assert.notNull(deviceAuthorizationResponse, "deviceAuthorizationResponse cannot be null");
|
||||||
|
return new Builder(deviceAuthorizationResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link OAuth2DeviceAuthorizationResponse}.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private final String deviceCode;
|
||||||
|
|
||||||
|
private final String userCode;
|
||||||
|
|
||||||
|
private String verificationUri;
|
||||||
|
|
||||||
|
private String verificationUriComplete;
|
||||||
|
|
||||||
|
private long expiresIn;
|
||||||
|
|
||||||
|
private long interval;
|
||||||
|
|
||||||
|
private Map<String, Object> additionalParameters;
|
||||||
|
|
||||||
|
private Builder(OAuth2DeviceAuthorizationResponse response) {
|
||||||
|
OAuth2DeviceCode deviceCode = response.getDeviceCode();
|
||||||
|
OAuth2UserCode userCode = response.getUserCode();
|
||||||
|
this.deviceCode = deviceCode.getTokenValue();
|
||||||
|
this.userCode = userCode.getTokenValue();
|
||||||
|
this.verificationUri = response.getVerificationUri();
|
||||||
|
this.verificationUriComplete = response.getVerificationUriComplete();
|
||||||
|
this.expiresIn = ChronoUnit.SECONDS.between(deviceCode.getIssuedAt(), deviceCode.getExpiresAt());
|
||||||
|
this.interval = response.getInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder(OAuth2DeviceCode deviceCode, OAuth2UserCode userCode) {
|
||||||
|
this.deviceCode = deviceCode.getTokenValue();
|
||||||
|
this.userCode = userCode.getTokenValue();
|
||||||
|
this.expiresIn = ChronoUnit.SECONDS.between(deviceCode.getIssuedAt(), deviceCode.getExpiresAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder(String deviceCode, String userCode) {
|
||||||
|
this.deviceCode = deviceCode;
|
||||||
|
this.userCode = userCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the end-user verification URI.
|
||||||
|
* @param verificationUri the end-user verification URI
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder verificationUri(String verificationUri) {
|
||||||
|
this.verificationUri = verificationUri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the end-user verification URI that includes the user code.
|
||||||
|
* @param verificationUriComplete the end-user verification URI that includes the
|
||||||
|
* user code
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder verificationUriComplete(String verificationUriComplete) {
|
||||||
|
this.verificationUriComplete = verificationUriComplete;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lifetime (in seconds) of the device code and user code.
|
||||||
|
* @param expiresIn the lifetime (in seconds) of the device code and user code
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder expiresIn(long expiresIn) {
|
||||||
|
this.expiresIn = expiresIn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum amount of time (in seconds) that the client should wait
|
||||||
|
* between polling requests to the token endpoint.
|
||||||
|
* @param interval the minimum amount of time between polling requests
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder interval(long interval) {
|
||||||
|
this.interval = interval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the additional parameters returned in the response.
|
||||||
|
* @param additionalParameters the additional parameters returned in the response
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder additionalParameters(Map<String, Object> additionalParameters) {
|
||||||
|
this.additionalParameters = additionalParameters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new {@link OAuth2DeviceAuthorizationResponse}.
|
||||||
|
* @return a {@link OAuth2DeviceAuthorizationResponse}
|
||||||
|
*/
|
||||||
|
public OAuth2DeviceAuthorizationResponse build() {
|
||||||
|
Assert.hasText(this.verificationUri, "verificationUri cannot be empty");
|
||||||
|
Assert.isTrue(this.expiresIn > 0, "expiresIn must be greater than zero");
|
||||||
|
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plusSeconds(this.expiresIn);
|
||||||
|
OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(this.deviceCode, issuedAt, expiresAt);
|
||||||
|
OAuth2UserCode userCode = new OAuth2UserCode(this.userCode, issuedAt, expiresAt);
|
||||||
|
|
||||||
|
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = new OAuth2DeviceAuthorizationResponse();
|
||||||
|
deviceAuthorizationResponse.deviceCode = deviceCode;
|
||||||
|
deviceAuthorizationResponse.userCode = userCode;
|
||||||
|
deviceAuthorizationResponse.verificationUri = this.verificationUri;
|
||||||
|
deviceAuthorizationResponse.verificationUriComplete = this.verificationUriComplete;
|
||||||
|
deviceAuthorizationResponse.interval = this.interval;
|
||||||
|
deviceAuthorizationResponse.additionalParameters = Collections
|
||||||
|
.unmodifiableMap(CollectionUtils.isEmpty(this.additionalParameters) ? Collections.emptyMap()
|
||||||
|
: this.additionalParameters);
|
||||||
|
|
||||||
|
return deviceAuthorizationResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -22,6 +22,7 @@ package org.springframework.security.oauth2.core.endpoint;
|
|||||||
* endpoint.
|
* endpoint.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Steve Riesenberg
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-11.2">11.2
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-11.2">11.2
|
||||||
* OAuth Parameters Registry</a>
|
* OAuth Parameters Registry</a>
|
||||||
@ -150,6 +151,38 @@ public final class OAuth2ParameterNames {
|
|||||||
*/
|
*/
|
||||||
public static final String TOKEN_TYPE_HINT = "token_type_hint";
|
public static final String TOKEN_TYPE_HINT = "token_type_hint";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code device_code} - used in Device Authorization Request and Device Authorization
|
||||||
|
* Response.
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final String DEVICE_CODE = "device_code";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code user_code} - used in Device Authorization Request and Device Authorization
|
||||||
|
* Response.
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final String USER_CODE = "user_code";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code verification_uri} - Used in Device Authorization Response.
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final String VERIFICATION_URI = "verification_uri";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code verification_uri_complete} - Used in Device Authorization Response.
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final String VERIFICATION_URI_COMPLETE = "verification_uri_complete";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code interval} - Used in Device Authorization Response.
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public static final String INTERVAL = "interval";
|
||||||
|
|
||||||
private OAuth2ParameterNames() {
|
private OAuth2ParameterNames() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core.http.converter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.http.HttpInputMessage;
|
||||||
|
import org.springframework.http.HttpOutputMessage;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link HttpMessageConverter} for an {@link OAuth2DeviceAuthorizationResponse OAuth
|
||||||
|
* 2.0 Device Authorization Response}.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 6.1
|
||||||
|
* @see AbstractHttpMessageConverter
|
||||||
|
* @see OAuth2DeviceAuthorizationResponse
|
||||||
|
*/
|
||||||
|
public class OAuth2DeviceAuthorizationResponseHttpMessageConverter
|
||||||
|
extends AbstractHttpMessageConverter<OAuth2DeviceAuthorizationResponse> {
|
||||||
|
|
||||||
|
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
private final GenericHttpMessageConverter<Object> jsonMessageConvereter = HttpMessageConverters
|
||||||
|
.getJsonMessageConverter();
|
||||||
|
|
||||||
|
private Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter = new DefaultMapOAuth2DeviceAuthorizationResponseConverter();
|
||||||
|
|
||||||
|
private Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> deviceAuthorizationResponseParametersConverter = new DefaultOAuth2DeviceAuthorizationResponseMapConverter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean supports(Class<?> clazz) {
|
||||||
|
return OAuth2DeviceAuthorizationResponse.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected OAuth2DeviceAuthorizationResponse readInternal(Class<? extends OAuth2DeviceAuthorizationResponse> clazz,
|
||||||
|
HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> deviceAuthorizationResponseParameters = (Map<String, Object>) this.jsonMessageConvereter
|
||||||
|
.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||||
|
return this.deviceAuthorizationResponseConverter.convert(deviceAuthorizationResponseParameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new HttpMessageNotReadableException(
|
||||||
|
"An error occurred reading the OAuth 2.0 Device Authorization Response: " + ex.getMessage(), ex,
|
||||||
|
inputMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeInternal(OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse,
|
||||||
|
HttpOutputMessage outputMessage) throws HttpMessageNotWritableException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> deviceauthorizationResponseParameters = this.deviceAuthorizationResponseParametersConverter
|
||||||
|
.convert(deviceAuthorizationResponse);
|
||||||
|
this.jsonMessageConvereter.write(deviceauthorizationResponseParameters, STRING_OBJECT_MAP.getType(),
|
||||||
|
MediaType.APPLICATION_JSON, outputMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new HttpMessageNotWritableException(
|
||||||
|
"An error occurred writing the OAuth 2.0 Device Authorization Response: " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link Converter} used for converting the OAuth 2.0 Device Authorization
|
||||||
|
* Response parameters to an {@link OAuth2DeviceAuthorizationResponse}.
|
||||||
|
* @param deviceAuthorizationResponseConverter the {@link Converter} used for
|
||||||
|
* converting to an {@link OAuth2DeviceAuthorizationResponse}
|
||||||
|
*/
|
||||||
|
public void setDeviceAuthorizationResponseConverter(
|
||||||
|
Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter) {
|
||||||
|
Assert.notNull(deviceAuthorizationResponseConverter, "deviceAuthorizationResponseConverter cannot be null");
|
||||||
|
this.deviceAuthorizationResponseConverter = deviceAuthorizationResponseConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link Converter} used for converting the
|
||||||
|
* {@link OAuth2DeviceAuthorizationResponse} to a {@code Map} representation of the
|
||||||
|
* OAuth 2.0 Device Authorization Response parameters.
|
||||||
|
* @param deviceAuthorizationResponseParametersConverter the {@link Converter} used
|
||||||
|
* for converting to a {@code Map} representation of the Device Authorization Response
|
||||||
|
* parameters
|
||||||
|
*/
|
||||||
|
public void setDeviceAuthorizationResponseParametersConverter(
|
||||||
|
Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> deviceAuthorizationResponseParametersConverter) {
|
||||||
|
Assert.notNull(deviceAuthorizationResponseParametersConverter,
|
||||||
|
"deviceAuthorizationResponseParametersConverter cannot be null");
|
||||||
|
this.deviceAuthorizationResponseParametersConverter = deviceAuthorizationResponseParametersConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DefaultMapOAuth2DeviceAuthorizationResponseConverter
|
||||||
|
implements Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> {
|
||||||
|
|
||||||
|
private static final Set<String> DEVICE_AUTHORIZATION_RESPONSE_PARAMETER_NAMES = new HashSet<>(
|
||||||
|
Arrays.asList(OAuth2ParameterNames.DEVICE_CODE, OAuth2ParameterNames.USER_CODE,
|
||||||
|
OAuth2ParameterNames.VERIFICATION_URI, OAuth2ParameterNames.VERIFICATION_URI_COMPLETE,
|
||||||
|
OAuth2ParameterNames.EXPIRES_IN, OAuth2ParameterNames.INTERVAL));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2DeviceAuthorizationResponse convert(Map<String, Object> parameters) {
|
||||||
|
String deviceCode = getParameterValue(parameters, OAuth2ParameterNames.DEVICE_CODE);
|
||||||
|
String userCode = getParameterValue(parameters, OAuth2ParameterNames.USER_CODE);
|
||||||
|
String verificationUri = getParameterValue(parameters, OAuth2ParameterNames.VERIFICATION_URI);
|
||||||
|
String verificationUriComplete = getParameterValue(parameters,
|
||||||
|
OAuth2ParameterNames.VERIFICATION_URI_COMPLETE);
|
||||||
|
long expiresIn = getParameterValue(parameters, OAuth2ParameterNames.EXPIRES_IN, 0L);
|
||||||
|
long interval = getParameterValue(parameters, OAuth2ParameterNames.INTERVAL, 0L);
|
||||||
|
Map<String, Object> additionalParameters = new LinkedHashMap<>();
|
||||||
|
parameters.forEach((key, value) -> {
|
||||||
|
if (!DEVICE_AUTHORIZATION_RESPONSE_PARAMETER_NAMES.contains(key)) {
|
||||||
|
additionalParameters.put(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// @formatter:off
|
||||||
|
return OAuth2DeviceAuthorizationResponse.with(deviceCode, userCode)
|
||||||
|
.verificationUri(verificationUri)
|
||||||
|
.verificationUriComplete(verificationUriComplete)
|
||||||
|
.expiresIn(expiresIn)
|
||||||
|
.interval(interval)
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getParameterValue(Map<String, Object> parameters, String parameterName) {
|
||||||
|
Object obj = parameters.get(parameterName);
|
||||||
|
return (obj != null) ? obj.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getParameterValue(Map<String, Object> tokenResponseParameters, String parameterName,
|
||||||
|
long defaultValue) {
|
||||||
|
long parameterValue = defaultValue;
|
||||||
|
|
||||||
|
Object obj = tokenResponseParameters.get(parameterName);
|
||||||
|
if (obj != null) {
|
||||||
|
// Final classes Long and Integer do not need to be coerced
|
||||||
|
if (obj.getClass() == Long.class) {
|
||||||
|
parameterValue = (Long) obj;
|
||||||
|
}
|
||||||
|
else if (obj.getClass() == Integer.class) {
|
||||||
|
parameterValue = (Integer) obj;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Attempt to coerce to a long (typically from a String)
|
||||||
|
try {
|
||||||
|
parameterValue = Long.parseLong(obj.toString());
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DefaultOAuth2DeviceAuthorizationResponseMapConverter
|
||||||
|
implements Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> convert(OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse) {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put(OAuth2ParameterNames.DEVICE_CODE,
|
||||||
|
deviceAuthorizationResponse.getDeviceCode().getTokenValue());
|
||||||
|
parameters.put(OAuth2ParameterNames.USER_CODE, deviceAuthorizationResponse.getUserCode().getTokenValue());
|
||||||
|
parameters.put(OAuth2ParameterNames.VERIFICATION_URI, deviceAuthorizationResponse.getVerificationUri());
|
||||||
|
if (StringUtils.hasText(deviceAuthorizationResponse.getVerificationUriComplete())) {
|
||||||
|
parameters.put(OAuth2ParameterNames.VERIFICATION_URI_COMPLETE,
|
||||||
|
deviceAuthorizationResponse.getVerificationUriComplete());
|
||||||
|
}
|
||||||
|
parameters.put(OAuth2ParameterNames.EXPIRES_IN, getExpiresIn(deviceAuthorizationResponse));
|
||||||
|
if (deviceAuthorizationResponse.getInterval() > 0) {
|
||||||
|
parameters.put(OAuth2ParameterNames.INTERVAL, deviceAuthorizationResponse.getInterval());
|
||||||
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(deviceAuthorizationResponse.getAdditionalParameters())) {
|
||||||
|
parameters.putAll(deviceAuthorizationResponse.getAdditionalParameters());
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getExpiresIn(OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse) {
|
||||||
|
if (deviceAuthorizationResponse.getDeviceCode().getExpiresAt() != null) {
|
||||||
|
return ChronoUnit.SECONDS.between(Instant.now(),
|
||||||
|
deviceAuthorizationResponse.getDeviceCode().getExpiresAt());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core.http.converter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||||
|
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||||
|
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2DeviceAuthorizationResponseHttpMessageConverter}.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
*/
|
||||||
|
public class OAuth2DeviceAuthorizationResponseHttpMessageConverterTest {
|
||||||
|
|
||||||
|
private OAuth2DeviceAuthorizationResponseHttpMessageConverter messageConverter;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
this.messageConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsWhenOAuth2DeviceAuthorizationResponseThenTrue() {
|
||||||
|
assertThat(this.messageConverter.supports(OAuth2DeviceAuthorizationResponse.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDeviceAuthorizationResponseConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> this.messageConverter.setDeviceAuthorizationResponseConverter(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setDeviceAuthorizationResponseParametersConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> this.messageConverter.setDeviceAuthorizationResponseParametersConverter(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readInternalWhenSuccessfulResponseWithAllParametersThenReadOAuth2DeviceAuthorizationResponse() {
|
||||||
|
// @formatter:off
|
||||||
|
String authorizationResponse = """
|
||||||
|
{
|
||||||
|
"device_code": "GmRhm_DnyEy",
|
||||||
|
"user_code": "WDJB-MJHT",
|
||||||
|
"verification_uri": "https://example.com/device",
|
||||||
|
"verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT",
|
||||||
|
"expires_in": 1800,
|
||||||
|
"interval": 5,
|
||||||
|
"custom_parameter_1": "custom-value-1",
|
||||||
|
"custom_parameter_2": "custom-value-2"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
// @formatter:on
|
||||||
|
MockClientHttpResponse response = new MockClientHttpResponse(authorizationResponse.getBytes(), HttpStatus.OK);
|
||||||
|
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = this.messageConverter
|
||||||
|
.readInternal(OAuth2DeviceAuthorizationResponse.class, response);
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getTokenValue())
|
||||||
|
.isEqualTo("GmRhm_DnyEy");
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getIssuedAt()).isNotNull();
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getExpiresAt())
|
||||||
|
.isBeforeOrEqualTo(Instant.now().plusSeconds(1800));
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getTokenValue()).isEqualTo("WDJB-MJHT");
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getIssuedAt())
|
||||||
|
.isEqualTo(deviceAuthorizationResponse.getDeviceCode().getIssuedAt());
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getExpiresAt())
|
||||||
|
.isEqualTo(deviceAuthorizationResponse.getDeviceCode().getExpiresAt());
|
||||||
|
assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo("https://example.com/device");
|
||||||
|
assertThat(deviceAuthorizationResponse.getVerificationUriComplete())
|
||||||
|
.isEqualTo("https://example.com/device?user_code=WDJB-MJHT");
|
||||||
|
assertThat(deviceAuthorizationResponse.getInterval()).isEqualTo(5);
|
||||||
|
assertThat(deviceAuthorizationResponse.getAdditionalParameters()).containsExactly(
|
||||||
|
entry("custom_parameter_1", "custom-value-1"), entry("custom_parameter_2", "custom-value-2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readInternalWhenSuccessfulResponseWithNullValuesThenReadOAuth2DeviceAuthorizationResponse() {
|
||||||
|
// @formatter:off
|
||||||
|
String authorizationResponse = """
|
||||||
|
{
|
||||||
|
"device_code": "GmRhm_DnyEy",
|
||||||
|
"user_code": "WDJB-MJHT",
|
||||||
|
"verification_uri": "https://example.com/device",
|
||||||
|
"verification_uri_complete": null,
|
||||||
|
"expires_in": 1800,
|
||||||
|
"interval": null
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
// @formatter:on
|
||||||
|
MockClientHttpResponse response = new MockClientHttpResponse(authorizationResponse.getBytes(), HttpStatus.OK);
|
||||||
|
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = this.messageConverter
|
||||||
|
.readInternal(OAuth2DeviceAuthorizationResponse.class, response);
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getTokenValue())
|
||||||
|
.isEqualTo("GmRhm_DnyEy");
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getIssuedAt()).isNotNull();
|
||||||
|
assertThat(deviceAuthorizationResponse.getDeviceCode().getExpiresAt())
|
||||||
|
.isBeforeOrEqualTo(Instant.now().plusSeconds(1800));
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getTokenValue()).isEqualTo("WDJB-MJHT");
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getIssuedAt())
|
||||||
|
.isEqualTo(deviceAuthorizationResponse.getDeviceCode().getIssuedAt());
|
||||||
|
assertThat(deviceAuthorizationResponse.getUserCode().getExpiresAt())
|
||||||
|
.isEqualTo(deviceAuthorizationResponse.getDeviceCode().getExpiresAt());
|
||||||
|
assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo("https://example.com/device");
|
||||||
|
assertThat(deviceAuthorizationResponse.getVerificationUriComplete()).isNull();
|
||||||
|
assertThat(deviceAuthorizationResponse.getInterval()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
|
||||||
|
Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter = mock(
|
||||||
|
Converter.class);
|
||||||
|
given(deviceAuthorizationResponseConverter.convert(any())).willThrow(RuntimeException.class);
|
||||||
|
this.messageConverter.setDeviceAuthorizationResponseConverter(deviceAuthorizationResponseConverter);
|
||||||
|
String authorizationResponse = "{}";
|
||||||
|
MockClientHttpResponse response = new MockClientHttpResponse(authorizationResponse.getBytes(), HttpStatus.OK);
|
||||||
|
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||||
|
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2DeviceAuthorizationResponse.class, response))
|
||||||
|
.withMessageContaining("An error occurred reading the OAuth 2.0 Device Authorization Response");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeInternalWhenOAuth2DeviceAuthorizationResponseThenWriteResponse() {
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put("custom_parameter_1", "custom-value-1");
|
||||||
|
additionalParameters.put("custom_parameter_2", "custom-value-2");
|
||||||
|
// @formatter:off
|
||||||
|
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse =
|
||||||
|
OAuth2DeviceAuthorizationResponse.with("GmRhm_DnyEy", "WDJB-MJHT")
|
||||||
|
.verificationUri("https://example.com/device")
|
||||||
|
.verificationUriComplete("https://example.com/device?user_code=WDJB-MJHT")
|
||||||
|
.expiresIn(1800)
|
||||||
|
.interval(5)
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
this.messageConverter.writeInternal(deviceAuthorizationResponse, outputMessage);
|
||||||
|
String authorizationResponse = outputMessage.getBodyAsString();
|
||||||
|
assertThat(authorizationResponse).contains("\"device_code\":\"GmRhm_DnyEy\"");
|
||||||
|
assertThat(authorizationResponse).contains("\"user_code\":\"WDJB-MJHT\"");
|
||||||
|
assertThat(authorizationResponse).contains("\"verification_uri\":\"https://example.com/device\"");
|
||||||
|
assertThat(authorizationResponse)
|
||||||
|
.contains("\"verification_uri_complete\":\"https://example.com/device?user_code=WDJB-MJHT\"");
|
||||||
|
assertThat(authorizationResponse).contains("\"expires_in\":");
|
||||||
|
assertThat(authorizationResponse).contains("\"interval\":5");
|
||||||
|
assertThat(authorizationResponse).contains("\"custom_parameter_1\":\"custom-value-1\"");
|
||||||
|
assertThat(authorizationResponse).contains("\"custom_parameter_2\":\"custom-value-2\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void writeInternalWhenConversionFailsThenThrowHttpMessageNotWritableException() {
|
||||||
|
Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> deviceAuthorizationResponseParametersConverter = mock(
|
||||||
|
Converter.class);
|
||||||
|
given(deviceAuthorizationResponseParametersConverter.convert(any())).willThrow(RuntimeException.class);
|
||||||
|
this.messageConverter
|
||||||
|
.setDeviceAuthorizationResponseParametersConverter(deviceAuthorizationResponseParametersConverter);
|
||||||
|
// @formatter:off
|
||||||
|
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse =
|
||||||
|
OAuth2DeviceAuthorizationResponse.with("GmRhm_DnyEy", "WDJB-MJHT")
|
||||||
|
.verificationUri("https://example.com/device")
|
||||||
|
.expiresIn(1800)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
assertThatExceptionOfType(HttpMessageNotWritableException.class)
|
||||||
|
.isThrownBy(() -> this.messageConverter.writeInternal(deviceAuthorizationResponse, outputMessage))
|
||||||
|
.withMessageContaining("An error occurred writing the OAuth 2.0 Device Authorization Response");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user