mirror of
https://github.com/ysoftdevs/oauth-playground-server.git
synced 2026-04-01 14:53:16 +02:00
DAG authorization pending - now correctly in JSON with standard code
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.ysoft.geecon;
|
package com.ysoft.geecon;
|
||||||
|
|
||||||
import com.ysoft.geecon.dto.*;
|
import com.ysoft.geecon.dto.*;
|
||||||
|
import com.ysoft.geecon.error.ErrorResponse;
|
||||||
import com.ysoft.geecon.error.OAuthException;
|
import com.ysoft.geecon.error.OAuthException;
|
||||||
import com.ysoft.geecon.repo.ClientsRepo;
|
import com.ysoft.geecon.repo.ClientsRepo;
|
||||||
import com.ysoft.geecon.repo.SessionsRepo;
|
import com.ysoft.geecon.repo.SessionsRepo;
|
||||||
@@ -121,7 +122,7 @@ public class OAuthResource {
|
|||||||
return switch (params.getGrantType()) {
|
return switch (params.getGrantType()) {
|
||||||
case "authorization_code" -> redeemAuthorizationCode(params);
|
case "authorization_code" -> redeemAuthorizationCode(params);
|
||||||
case "urn:ietf:params:oauth:grant-type:device_code" -> redeemDeviceCode(params);
|
case "urn:ietf:params:oauth:grant-type:device_code" -> redeemDeviceCode(params);
|
||||||
default -> throw new OAuthException("Unsupported grant type");
|
default -> throw new OAuthException(ErrorResponse.Error.invalid_request, "Unsupported grant type");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,23 +150,23 @@ public class OAuthResource {
|
|||||||
|
|
||||||
private AccessTokenResponse redeemAuthorizationCode(TokenParams params) {
|
private AccessTokenResponse redeemAuthorizationCode(TokenParams params) {
|
||||||
var session = sessionsRepo.redeemAuthorizationCode(params.getCode())
|
var session = sessionsRepo.redeemAuthorizationCode(params.getCode())
|
||||||
.orElseThrow(() -> new OAuthException("Invalid code"));
|
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.access_denied, "Invalid code"));
|
||||||
validateClient(params, session);
|
validateClient(params, session);
|
||||||
if (!session.validateCodeChallenge(params.getCodeVerifier())) {
|
if (!session.validateCodeChallenge(params.getCodeVerifier())) {
|
||||||
throw new OAuthException("Invalid code verifier");
|
throw new OAuthException(ErrorResponse.Error.access_denied, "Invalid code verifier");
|
||||||
}
|
}
|
||||||
return session.tokens();
|
return session.tokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessTokenResponse redeemDeviceCode(TokenParams params) {
|
private AccessTokenResponse redeemDeviceCode(TokenParams params) {
|
||||||
var session = sessionsRepo.getByAuthorizationCode(params.getDeviceCode())
|
var session = sessionsRepo.getByAuthorizationCode(params.getDeviceCode())
|
||||||
.orElseThrow(() -> new OAuthException("Invalid code"));
|
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.access_denied, "Invalid device code"));
|
||||||
validateClient(params, session);
|
validateClient(params, session);
|
||||||
if (session.tokens() != null) {
|
if (session.tokens() != null) {
|
||||||
sessionsRepo.redeemAuthorizationCode(params.getDeviceCode());
|
sessionsRepo.redeemAuthorizationCode(params.getDeviceCode());
|
||||||
return session.tokens();
|
return session.tokens();
|
||||||
} else {
|
} else {
|
||||||
throw new OAuthException("Authorization pending");
|
throw new OAuthException(ErrorResponse.Error.authorization_pending, "Authorization pending");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,24 +178,24 @@ public class OAuthResource {
|
|||||||
|
|
||||||
private OAuthClient validateClient(AuthParams params) {
|
private OAuthClient validateClient(AuthParams params) {
|
||||||
var client = clientsRepo.getClient(params.getClientId())
|
var client = clientsRepo.getClient(params.getClientId())
|
||||||
.orElseThrow(() -> new RuntimeException("Not a valid client"));
|
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.invalid_request, "Not a valid client"));
|
||||||
if (!client.validateRedirectUri(params.getRedirectUri())) {
|
if (!client.validateRedirectUri(params.getRedirectUri())) {
|
||||||
throw new RuntimeException("Invalid redirect URI");
|
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid redirect URI");
|
||||||
}
|
}
|
||||||
if (StringUtil.isNullOrEmpty(params.getState())) {
|
if (StringUtil.isNullOrEmpty(params.getState())) {
|
||||||
throw new RuntimeException("Invalid state");
|
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid state");
|
||||||
}
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuthClient validateClient(TokenParams params, AuthorizationSession session) {
|
private OAuthClient validateClient(TokenParams params, AuthorizationSession session) {
|
||||||
var client = clientsRepo.getClient(params.getClientId())
|
var client = clientsRepo.getClient(params.getClientId())
|
||||||
.orElseThrow(() -> new RuntimeException("Not a valid client"));
|
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.invalid_request, "Not a valid client"));
|
||||||
if (!session.validateRedirectUri(params.getRedirectUri())) {
|
if (!session.validateRedirectUri(params.getRedirectUri())) {
|
||||||
throw new RuntimeException("Invalid redirect URI");
|
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid redirect URI");
|
||||||
}
|
}
|
||||||
if (!client.validateSecret(params.getClientSecret())) {
|
if (!client.validateSecret(params.getClientSecret())) {
|
||||||
throw new RuntimeException("Invalid secret");
|
throw new OAuthException(ErrorResponse.Error.unauthorized_client, "Invalid secret");
|
||||||
}
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main/java/com/ysoft/geecon/error/ErrorResponse.java
Normal file
10
src/main/java/com/ysoft/geecon/error/ErrorResponse.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ysoft.geecon.error;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public record ErrorResponse(@JsonProperty("error") Error error,
|
||||||
|
@JsonProperty("error_description") String description) {
|
||||||
|
public enum Error {
|
||||||
|
invalid_request, unauthorized_client, access_denied, invalid_scope, server_error, temporarily_unavailable, authorization_pending
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,6 @@ class ExceptionMappers {
|
|||||||
|
|
||||||
@CheckedTemplate
|
@CheckedTemplate
|
||||||
public static class Templates {
|
public static class Templates {
|
||||||
public static native TemplateInstance error(OAuthException.ErrorResponse response);
|
public static native TemplateInstance error(ErrorResponse response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,24 @@
|
|||||||
package com.ysoft.geecon.error;
|
package com.ysoft.geecon.error;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
public class OAuthException extends RuntimeException {
|
public class OAuthException extends RuntimeException {
|
||||||
private final ErrorResponse response;
|
private final ErrorResponse response;
|
||||||
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-09.html#name-error-response-2
|
|
||||||
public OAuthException(ErrorResponse response) {
|
public OAuthException(ErrorResponse response) {
|
||||||
super("OAuth error: " + response.error() + " " + response.description());
|
super("OAuth error: " + response.error() + " " + response.description());
|
||||||
this.response = response;
|
this.response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OAuthException(String error, String description) {
|
public OAuthException(ErrorResponse.Error error, String description) {
|
||||||
this(new ErrorResponse(error, description));
|
this(new ErrorResponse(error, description));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public OAuthException(String message) {
|
public OAuthException(String message) {
|
||||||
this(message, message);
|
this(ErrorResponse.Error.server_error, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorResponse getResponse() {
|
public ErrorResponse getResponse() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ErrorResponse(@JsonProperty("error") String error,
|
|
||||||
@JsonProperty("error_description") String description) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ysoft.geecon;
|
|||||||
import com.ysoft.geecon.dto.DeviceResponse;
|
import com.ysoft.geecon.dto.DeviceResponse;
|
||||||
import com.ysoft.geecon.dto.OAuthClient;
|
import com.ysoft.geecon.dto.OAuthClient;
|
||||||
import com.ysoft.geecon.dto.User;
|
import com.ysoft.geecon.dto.User;
|
||||||
|
import com.ysoft.geecon.error.ErrorResponse;
|
||||||
import com.ysoft.geecon.helpers.ConsentScreen;
|
import com.ysoft.geecon.helpers.ConsentScreen;
|
||||||
import com.ysoft.geecon.helpers.DeviceAuthorizationGrantFlow;
|
import com.ysoft.geecon.helpers.DeviceAuthorizationGrantFlow;
|
||||||
import com.ysoft.geecon.helpers.DeviceCodeScreen;
|
import com.ysoft.geecon.helpers.DeviceCodeScreen;
|
||||||
@@ -71,6 +72,7 @@ public class DeviceAuthGrantTest {
|
|||||||
public void deviceAuthGrant_authorizationPending() throws IOException {
|
public void deviceAuthGrant_authorizationPending() throws IOException {
|
||||||
DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT);
|
DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT);
|
||||||
flow.start();
|
flow.start();
|
||||||
flow.exchangeDeviceCodeError();
|
ErrorResponse errorResponse = flow.exchangeDeviceCodeError();
|
||||||
|
assertThat(errorResponse.error(), is(ErrorResponse.Error.authorization_pending));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package com.ysoft.geecon.helpers;
|
|||||||
import com.ysoft.geecon.dto.AccessTokenResponse;
|
import com.ysoft.geecon.dto.AccessTokenResponse;
|
||||||
import com.ysoft.geecon.dto.DeviceResponse;
|
import com.ysoft.geecon.dto.DeviceResponse;
|
||||||
import com.ysoft.geecon.dto.OAuthClient;
|
import com.ysoft.geecon.dto.OAuthClient;
|
||||||
import com.ysoft.geecon.error.OAuthException;
|
import com.ysoft.geecon.error.ErrorResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public class DeviceAuthorizationGrantFlow {
|
|||||||
.extract().as(AccessTokenResponse.class);
|
.extract().as(AccessTokenResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OAuthException.ErrorResponse exchangeDeviceCodeError() {
|
public ErrorResponse exchangeDeviceCodeError() {
|
||||||
return given()
|
return given()
|
||||||
.formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
|
.formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
|
||||||
.formParam("client_id", client.clientId())
|
.formParam("client_id", client.clientId())
|
||||||
@@ -68,6 +68,6 @@ public class DeviceAuthorizationGrantFlow {
|
|||||||
.contentType(JSON)
|
.contentType(JSON)
|
||||||
.body("error", is(notNullValue()))
|
.body("error", is(notNullValue()))
|
||||||
.body("error_description", is(notNullValue()))
|
.body("error_description", is(notNullValue()))
|
||||||
.extract().as(OAuthException.ErrorResponse.class);
|
.extract().as(ErrorResponse.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user