DAG authorization pending - now correctly in JSON with standard code

This commit is contained in:
Dusan Jakub
2023-09-19 10:30:56 +02:00
parent 9a7a437153
commit d6bd44e799
6 changed files with 31 additions and 24 deletions

View File

@@ -1,6 +1,7 @@
package com.ysoft.geecon;
import com.ysoft.geecon.dto.*;
import com.ysoft.geecon.error.ErrorResponse;
import com.ysoft.geecon.error.OAuthException;
import com.ysoft.geecon.repo.ClientsRepo;
import com.ysoft.geecon.repo.SessionsRepo;
@@ -121,7 +122,7 @@ public class OAuthResource {
return switch (params.getGrantType()) {
case "authorization_code" -> redeemAuthorizationCode(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) {
var session = sessionsRepo.redeemAuthorizationCode(params.getCode())
.orElseThrow(() -> new OAuthException("Invalid code"));
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.access_denied, "Invalid code"));
validateClient(params, session);
if (!session.validateCodeChallenge(params.getCodeVerifier())) {
throw new OAuthException("Invalid code verifier");
throw new OAuthException(ErrorResponse.Error.access_denied, "Invalid code verifier");
}
return session.tokens();
}
private AccessTokenResponse redeemDeviceCode(TokenParams params) {
var session = sessionsRepo.getByAuthorizationCode(params.getDeviceCode())
.orElseThrow(() -> new OAuthException("Invalid code"));
.orElseThrow(() -> new OAuthException(ErrorResponse.Error.access_denied, "Invalid device code"));
validateClient(params, session);
if (session.tokens() != null) {
sessionsRepo.redeemAuthorizationCode(params.getDeviceCode());
return session.tokens();
} 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) {
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())) {
throw new RuntimeException("Invalid redirect URI");
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid redirect URI");
}
if (StringUtil.isNullOrEmpty(params.getState())) {
throw new RuntimeException("Invalid state");
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid state");
}
return client;
}
private OAuthClient validateClient(TokenParams params, AuthorizationSession session) {
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())) {
throw new RuntimeException("Invalid redirect URI");
throw new OAuthException(ErrorResponse.Error.invalid_request, "Invalid redirect URI");
}
if (!client.validateSecret(params.getClientSecret())) {
throw new RuntimeException("Invalid secret");
throw new OAuthException(ErrorResponse.Error.unauthorized_client, "Invalid secret");
}
return client;
}

View 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
}
}

View File

@@ -35,6 +35,6 @@ class ExceptionMappers {
@CheckedTemplate
public static class Templates {
public static native TemplateInstance error(OAuthException.ErrorResponse response);
public static native TemplateInstance error(ErrorResponse response);
}
}

View File

@@ -1,30 +1,24 @@
package com.ysoft.geecon.error;
import com.fasterxml.jackson.annotation.JsonProperty;
public class OAuthException extends RuntimeException {
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) {
super("OAuth error: " + response.error() + " " + response.description());
this.response = response;
}
public OAuthException(String error, String description) {
public OAuthException(ErrorResponse.Error error, String description) {
this(new ErrorResponse(error, description));
}
@Deprecated
public OAuthException(String message) {
this(message, message);
this(ErrorResponse.Error.server_error, message);
}
public ErrorResponse getResponse() {
return response;
}
public record ErrorResponse(@JsonProperty("error") String error,
@JsonProperty("error_description") String description) {
}
}

View File

@@ -3,6 +3,7 @@ package com.ysoft.geecon;
import com.ysoft.geecon.dto.DeviceResponse;
import com.ysoft.geecon.dto.OAuthClient;
import com.ysoft.geecon.dto.User;
import com.ysoft.geecon.error.ErrorResponse;
import com.ysoft.geecon.helpers.ConsentScreen;
import com.ysoft.geecon.helpers.DeviceAuthorizationGrantFlow;
import com.ysoft.geecon.helpers.DeviceCodeScreen;
@@ -71,6 +72,7 @@ public class DeviceAuthGrantTest {
public void deviceAuthGrant_authorizationPending() throws IOException {
DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT);
flow.start();
flow.exchangeDeviceCodeError();
ErrorResponse errorResponse = flow.exchangeDeviceCodeError();
assertThat(errorResponse.error(), is(ErrorResponse.Error.authorization_pending));
}
}

View File

@@ -3,7 +3,7 @@ package com.ysoft.geecon.helpers;
import com.ysoft.geecon.dto.AccessTokenResponse;
import com.ysoft.geecon.dto.DeviceResponse;
import com.ysoft.geecon.dto.OAuthClient;
import com.ysoft.geecon.error.OAuthException;
import com.ysoft.geecon.error.ErrorResponse;
import java.io.IOException;
@@ -56,7 +56,7 @@ public class DeviceAuthorizationGrantFlow {
.extract().as(AccessTokenResponse.class);
}
public OAuthException.ErrorResponse exchangeDeviceCodeError() {
public ErrorResponse exchangeDeviceCodeError() {
return given()
.formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
.formParam("client_id", client.clientId())
@@ -68,6 +68,6 @@ public class DeviceAuthorizationGrantFlow {
.contentType(JSON)
.body("error", is(notNullValue()))
.body("error_description", is(notNullValue()))
.extract().as(OAuthException.ErrorResponse.class);
.extract().as(ErrorResponse.class);
}
}