diff --git a/src/main/java/com/ysoft/geecon/OAuthResource.java b/src/main/java/com/ysoft/geecon/OAuthResource.java index dfa61c0..7d9e4c9 100644 --- a/src/main/java/com/ysoft/geecon/OAuthResource.java +++ b/src/main/java/com/ysoft/geecon/OAuthResource.java @@ -123,7 +123,8 @@ 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 OAuthApiException(ErrorResponse.Error.invalid_request, "Unsupported grant type"); + default -> + throw new OAuthApiException(ErrorResponse.Error.unsupported_grant_type, "Unsupported grant type"); }; } diff --git a/src/main/java/com/ysoft/geecon/error/ErrorResponse.java b/src/main/java/com/ysoft/geecon/error/ErrorResponse.java index 2fb9acb..1c7e198 100644 --- a/src/main/java/com/ysoft/geecon/error/ErrorResponse.java +++ b/src/main/java/com/ysoft/geecon/error/ErrorResponse.java @@ -5,7 +5,7 @@ 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, unsupported_response_type, + invalid_request, unauthorized_client, unsupported_response_type, unsupported_grant_type, access_denied, invalid_scope, server_error, temporarily_unavailable, authorization_pending } } diff --git a/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java b/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java index da68dfe..b33f16b 100644 --- a/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java +++ b/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java @@ -1,5 +1,6 @@ package com.ysoft.geecon; +import com.ysoft.geecon.dto.AccessTokenResponse; import com.ysoft.geecon.dto.OAuthClient; import com.ysoft.geecon.dto.User; import com.ysoft.geecon.error.ErrorResponse; @@ -53,9 +54,9 @@ public class AuthCodeGrantTest { assertThat(flow.getCode(), is(notNullValue())); assertThat(flow.getAccessToken(), is(nullValue())); - flow.exchangeCode(); + AccessTokenResponse accessTokenResponse = flow.exchangeCode().expectTokens(); - assertThat(flow.getAccessToken(), is(notNullValue())); + assertThat(accessTokenResponse.accessToken(), is(notNullValue())); } @Test @@ -94,9 +95,9 @@ public class AuthCodeGrantTest { assertThat(flow.getCode(), is(notNullValue())); assertThat(flow.getAccessToken(), is(nullValue())); - flow.exchangeCode(); + AccessTokenResponse accessTokenResponse = flow.exchangeCode().expectTokens(); - assertThat(flow.getAccessToken(), is(notNullValue())); + assertThat(accessTokenResponse.accessToken(), is(notNullValue())); } } \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java index 14d4e00..b6570fc 100644 --- a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java +++ b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java @@ -1,5 +1,6 @@ package com.ysoft.geecon; +import com.ysoft.geecon.dto.AccessTokenResponse; import com.ysoft.geecon.dto.DeviceResponse; import com.ysoft.geecon.dto.OAuthClient; import com.ysoft.geecon.dto.User; @@ -21,6 +22,7 @@ import java.io.IOException; import java.net.URI; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -57,7 +59,8 @@ public class DeviceAuthGrantTest { ConsentScreen consentScreen = loginScreen.submitCorrect("bob", "password"); consentScreen.submit(); - flow.exchangeDeviceCode(); + AccessTokenResponse accessTokenResponse = flow.exchangeDeviceCode().expectTokens(); + assertThat(accessTokenResponse.accessToken(), is(notNullValue())); } @Test @@ -69,10 +72,10 @@ public class DeviceAuthGrantTest { } @Test - public void deviceAuthGrant_authorizationPending() throws IOException { + public void deviceAuthGrant_authorizationPending() { DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT); flow.start(); - ErrorResponse errorResponse = flow.exchangeDeviceCodeError(); + ErrorResponse errorResponse = flow.exchangeDeviceCode().expectError(400); assertThat(errorResponse.error(), is(ErrorResponse.Error.authorization_pending)); } } \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/TokenEndpointTest.java b/src/test/java/com/ysoft/geecon/TokenEndpointTest.java new file mode 100644 index 0000000..61456d1 --- /dev/null +++ b/src/test/java/com/ysoft/geecon/TokenEndpointTest.java @@ -0,0 +1,33 @@ +package com.ysoft.geecon; + +import com.ysoft.geecon.dto.OAuthClient; +import com.ysoft.geecon.error.ErrorResponse; +import com.ysoft.geecon.helpers.TokenEndpointCall; +import com.ysoft.geecon.repo.ClientsRepo; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@QuarkusTest +public class TokenEndpointTest { + public static final OAuthClient CLIENT = new OAuthClient("deviceclient", "", null, null); + @Inject + ClientsRepo clientsRepo; + + @BeforeEach + void beforeAll() { + clientsRepo.register(CLIENT); + } + + @Test + public void invalidGrant() { + ErrorResponse errorResponse = new TokenEndpointCall(CLIENT).grantType("invalid").expectError(400); + assertThat(errorResponse.error(), is(ErrorResponse.Error.unsupported_grant_type)); + assertThat(errorResponse.description(), is(notNullValue())); + } +} \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/helpers/AuthorizationCodeFlow.java b/src/test/java/com/ysoft/geecon/helpers/AuthorizationCodeFlow.java index a043564..571a6b3 100644 --- a/src/test/java/com/ysoft/geecon/helpers/AuthorizationCodeFlow.java +++ b/src/test/java/com/ysoft/geecon/helpers/AuthorizationCodeFlow.java @@ -1,6 +1,5 @@ package com.ysoft.geecon.helpers; -import com.ysoft.geecon.dto.AccessTokenResponse; import com.ysoft.geecon.dto.OAuthClient; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; @@ -16,8 +15,6 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -104,31 +101,8 @@ public class AuthorizationCodeFlow { return query; } - public AccessTokenResponse exchangeCode() { - Map tokenForm = new HashMap<>(); - tokenForm.put("grant_type", "authorization_code"); - tokenForm.put("client_id", client.clientId()); - tokenForm.put("redirect_uri", client.redirectUri()); - tokenForm.put("code", code); - if (codeVerifier != null) { - tokenForm.put("code_verifier", codeVerifier); - } - - AccessTokenResponse accessTokenResponse = given() - .formParams(tokenForm) - .when() - .post("/auth/token") - .then() - .statusCode(200) - .contentType(JSON) - .body("token_type", is("Bearer")) - .body("expires_in", is(notNullValue())) - .body("access_token", is(notNullValue())) - .body("refresh_token", is(notNullValue())) - .extract().body().as(AccessTokenResponse.class); - accessToken = accessTokenResponse.accessToken(); - idToken = accessTokenResponse.idToken(); - return accessTokenResponse; + public TokenEndpointCall exchangeCode() { + return new TokenEndpointCall(client).authorizationCode(code, codeVerifier); } public String getState() { diff --git a/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java b/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java index 2a7bb65..233f719 100644 --- a/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java +++ b/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java @@ -1,9 +1,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.ErrorResponse; import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; @@ -37,35 +35,7 @@ public class DeviceAuthorizationGrantFlow { return deviceResponse; } - public AccessTokenResponse exchangeDeviceCode() { - return given() - .formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code") - .formParam("client_id", client.clientId()) - .formParam("device_code", deviceResponse.deviceCode()) - .when() - .post("/auth/token") - .then() - .statusCode(200) - .contentType(JSON) - .body("token_type", is(notNullValue())) - .body("expires_in", is(notNullValue())) - .body("access_token", is(notNullValue())) - .body("refresh_token", is(notNullValue())) - .extract().as(AccessTokenResponse.class); - } - - public ErrorResponse exchangeDeviceCodeError() { - return given() - .formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code") - .formParam("client_id", client.clientId()) - .formParam("device_code", deviceResponse.deviceCode()) - .when() - .post("/auth/token") - .then() - .statusCode(400) - .contentType(JSON) - .body("error", is(notNullValue())) - .body("error_description", is(notNullValue())) - .extract().as(ErrorResponse.class); + public TokenEndpointCall exchangeDeviceCode() { + return new TokenEndpointCall(client).deviceCode(deviceResponse.deviceCode()); } } diff --git a/src/test/java/com/ysoft/geecon/helpers/TokenEndpointCall.java b/src/test/java/com/ysoft/geecon/helpers/TokenEndpointCall.java new file mode 100644 index 0000000..2036ebd --- /dev/null +++ b/src/test/java/com/ysoft/geecon/helpers/TokenEndpointCall.java @@ -0,0 +1,74 @@ +package com.ysoft.geecon.helpers; + +import com.ysoft.geecon.dto.AccessTokenResponse; +import com.ysoft.geecon.dto.OAuthClient; +import com.ysoft.geecon.error.ErrorResponse; +import io.restassured.response.ValidatableResponse; + +import java.util.HashMap; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; + +public class TokenEndpointCall { + private final OAuthClient client; + private final Map tokenForm; + + public TokenEndpointCall(OAuthClient client) { + this.client = client; + + tokenForm = new HashMap<>(); + tokenForm.put("client_id", client.clientId()); + } + + public TokenEndpointCall authorizationCode(String code, String codeVerifier) { + tokenForm.put("grant_type", "authorization_code"); + tokenForm.put("redirect_uri", client.redirectUri()); + tokenForm.put("code", code); + if (codeVerifier != null) { + tokenForm.put("code_verifier", codeVerifier); + } + return this; + } + + public TokenEndpointCall deviceCode(String deviceCode) { + tokenForm.put("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); + tokenForm.put("device_code", deviceCode); + return this; + } + + public TokenEndpointCall grantType(String grantType) { + tokenForm.put("grant_type", grantType); + return this; + } + + public AccessTokenResponse expectTokens() { + return expect() + .statusCode(200) + .body("token_type", is("Bearer")) + .body("expires_in", is(notNullValue())) + .body("access_token", is(notNullValue())) + .body("refresh_token", is(notNullValue())) + .extract().body().as(AccessTokenResponse.class); + } + + public ErrorResponse expectError(int status) { + return expect() + .statusCode(status) + .body("error", is(notNullValue())) + .body("error_description", is(notNullValue())) + .extract().body().as(ErrorResponse.class); + } + + private ValidatableResponse expect() { + return given() + .formParams(tokenForm) + .when() + .post("/auth/token") + .then() + .contentType(JSON); + } +}