Skip to content

Add support for device authorization response #12853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -62,6 +62,12 @@ public final class AuthorizationGrantType implements Serializable {
public static final AuthorizationGrantType JWT_BEARER = new AuthorizationGrantType(
"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;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}

}
Loading