From fc039750b21cec9096eac1443dba841b1666b775 Mon Sep 17 00:00:00 2001 From: Dusan Jakub Date: Mon, 18 Sep 2023 18:29:42 +0200 Subject: [PATCH] fix verification url generation, rewrite DAG test --- .../java/com/ysoft/geecon/OAuthResource.java | 7 +- .../com/ysoft/geecon/dto/DeviceResponse.java | 4 +- .../com/ysoft/geecon/DeviceAuthGrantTest.java | 95 ++++++++----------- .../helpers/DeviceAuthorizationGrantFlow.java | 57 +++++++++++ .../geecon/helpers/DeviceCodeScreen.java | 23 +++++ .../com/ysoft/geecon/helpers/LoginScreen.java | 1 - 6 files changed, 128 insertions(+), 59 deletions(-) create mode 100644 src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java create mode 100644 src/test/java/com/ysoft/geecon/helpers/DeviceCodeScreen.java diff --git a/src/main/java/com/ysoft/geecon/OAuthResource.java b/src/main/java/com/ysoft/geecon/OAuthResource.java index e90f844..ed1a69f 100644 --- a/src/main/java/com/ysoft/geecon/OAuthResource.java +++ b/src/main/java/com/ysoft/geecon/OAuthResource.java @@ -13,6 +13,7 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.UriInfo; import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -46,6 +47,8 @@ public class OAuthResource { UsersRepo usersRepo; @Inject SessionsRepo sessionsRepo; + @Inject + UriInfo uriInfo; @GET @Produces(MediaType.TEXT_HTML) @@ -102,7 +105,9 @@ public class OAuthResource { return new DeviceResponse( sessionsRepo.generateAuthorizationCode(sessionId), sessionsRepo.generateUserCode(sessionId), - "http://verificationuri/device-login", + uriInfo.getBaseUriBuilder() + .path(OAuthResource.class) + .path(OAuthResource.class, "enterDeviceCode").build(), 10, 180 ); diff --git a/src/main/java/com/ysoft/geecon/dto/DeviceResponse.java b/src/main/java/com/ysoft/geecon/dto/DeviceResponse.java index 92ea27d..92ba522 100644 --- a/src/main/java/com/ysoft/geecon/dto/DeviceResponse.java +++ b/src/main/java/com/ysoft/geecon/dto/DeviceResponse.java @@ -2,10 +2,12 @@ package com.ysoft.geecon.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; + public record DeviceResponse( @JsonProperty("device_code") String deviceCode, @JsonProperty("user_code") String userCode, - @JsonProperty("verification_uri") String verificationUri, + @JsonProperty("verification_uri") URI verificationUri, @JsonProperty("interval") long interval, @JsonProperty("expires_in") long expiresIn ) { diff --git a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java index 25c0bb0..80ff55a 100644 --- a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java +++ b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java @@ -3,84 +3,67 @@ 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.helpers.ConsentScreen; +import com.ysoft.geecon.helpers.DeviceAuthorizationGrantFlow; +import com.ysoft.geecon.helpers.DeviceCodeScreen; +import com.ysoft.geecon.helpers.LoginScreen; import com.ysoft.geecon.repo.ClientsRepo; import com.ysoft.geecon.repo.UsersRepo; +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; -import org.jsoup.Jsoup; +import org.jsoup.HttpStatusException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; +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; @QuarkusTest public class DeviceAuthGrantTest { + + public static final OAuthClient CLIENT = new OAuthClient("deviceclient", "", null, null); @Inject ClientsRepo clientsRepo; @Inject UsersRepo usersRepo; + @TestHTTPResource("auth/device") + String deviceUri; + + @TestHTTPResource("auth/device-login") + URI deviceLoginUri; + + @BeforeEach + void beforeAll() { + clientsRepo.register(CLIENT); + usersRepo.register(new User("bob", "password")); + } + @Test - public void deviceAuthGrant_invalidCode() { - given().formParam("code", "somecode"). - when().post("/auth/device-login"). - then().statusCode(404); + public void deviceAuthGrant_invalidCode() throws IOException { + DeviceCodeScreen deviceCodeScreen = new DeviceCodeScreen(deviceLoginUri); + + HttpStatusException exception = assertThrows(HttpStatusException.class, () -> deviceCodeScreen.enterCode("somecode")); + assertThat(exception.getStatusCode(), is(404)); } @Test - public void deviceAuthGrant() { - clientsRepo.register(new OAuthClient("myclient", "", null, null)); - usersRepo.register(new User("bob", "password")); + public void deviceAuthGrant() throws IOException { + DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT); + DeviceResponse deviceResponse = flow.start(); - DeviceResponse deviceResponse = given(). - formParam("client_id", "myclient"). - when().post("/auth/device") - .then() - .statusCode(200) - .contentType(JSON) - .body("device_code", is(notNullValue())) - .body("user_code", is(notNullValue())) - .body("verification_uri", is(notNullValue())) - .body("interval", is(notNullValue())) - .body("expires_in", is(notNullValue())) - .extract().body().as(DeviceResponse.class); + DeviceCodeScreen deviceCodeScreen = new DeviceCodeScreen(deviceResponse.verificationUri()); + LoginScreen loginScreen = deviceCodeScreen.enterCode(deviceResponse.userCode()); - String deviceLogin = given().formParam("code", deviceResponse.userCode()). - when().post("/auth/device-login"). - then().statusCode(200) - .extract().body().asString(); + ConsentScreen consentScreen = loginScreen.submitCorrect("bob", "password"); + consentScreen.submit(); - String sessionId = Jsoup.parse(deviceLogin).getElementsByAttributeValue("name", "sessionId").first().attr("value"); - - given(). - formParam("sessionId", sessionId). - formParam("username", "bob"). - formParam("password", "password"). - when(). - post("auth") - .then().statusCode(200); - - given(). - formParam("sessionId", sessionId). - when(). - post("auth/consent") - .then().statusCode(200); - - given(). - formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code"). - formParam("client_id", "myclient"). - 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())); + flow.exchangeDeviceCode(); } } \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java b/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java new file mode 100644 index 0000000..5ea158f --- /dev/null +++ b/src/test/java/com/ysoft/geecon/helpers/DeviceAuthorizationGrantFlow.java @@ -0,0 +1,57 @@ +package com.ysoft.geecon.helpers; + +import com.ysoft.geecon.dto.AccessTokenResponse; +import com.ysoft.geecon.dto.DeviceResponse; +import com.ysoft.geecon.dto.OAuthClient; + +import java.io.IOException; + +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 DeviceAuthorizationGrantFlow { + private final String deviceUrl; + private final OAuthClient client; + private DeviceResponse deviceResponse; + + public DeviceAuthorizationGrantFlow(String deviceUrl, OAuthClient client) { + this.deviceUrl = deviceUrl; + this.client = client; + } + + public DeviceResponse start() throws IOException { + deviceResponse = given(). + formParam("client_id", client.clientId()). + when().post(deviceUrl) + .then() + .statusCode(200) + .contentType(JSON) + .body("device_code", is(notNullValue())) + .body("user_code", is(notNullValue())) + .body("verification_uri", is(notNullValue())) + .body("interval", is(notNullValue())) + .body("expires_in", is(notNullValue())) + .extract().body().as(DeviceResponse.class); + + 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); + } +} diff --git a/src/test/java/com/ysoft/geecon/helpers/DeviceCodeScreen.java b/src/test/java/com/ysoft/geecon/helpers/DeviceCodeScreen.java new file mode 100644 index 0000000..98bb840 --- /dev/null +++ b/src/test/java/com/ysoft/geecon/helpers/DeviceCodeScreen.java @@ -0,0 +1,23 @@ +package com.ysoft.geecon.helpers; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.FormElement; + +import java.io.IOException; +import java.net.URI; + +public class DeviceCodeScreen { + + private final FormElement form; + + public DeviceCodeScreen(URI verificationUrl) throws IOException { + this.form = Jsoup.connect(verificationUrl.toString()).get().expectForm("form"); + } + + public LoginScreen enterCode(String code) throws IOException { + form.getElementsByAttributeValue("name", "code").val(code); + Document login = form.submit().post(); + return new LoginScreen(login); + } +} diff --git a/src/test/java/com/ysoft/geecon/helpers/LoginScreen.java b/src/test/java/com/ysoft/geecon/helpers/LoginScreen.java index a05dbac..2c6a2c3 100644 --- a/src/test/java/com/ysoft/geecon/helpers/LoginScreen.java +++ b/src/test/java/com/ysoft/geecon/helpers/LoginScreen.java @@ -11,7 +11,6 @@ public class LoginScreen { public LoginScreen(Document doc) { this.form = doc.expectForm("form"); - ; } public Document submit(String username, String password) throws IOException {