mirror of
https://github.com/ysoftdevs/oauth-playground-server.git
synced 2026-07-01 18:41:41 +02:00
fix verification url generation, rewrite DAG test
This commit is contained in:
@@ -13,6 +13,7 @@ import jakarta.ws.rs.*;
|
|||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriBuilder;
|
import jakarta.ws.rs.core.UriBuilder;
|
||||||
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -46,6 +47,8 @@ public class OAuthResource {
|
|||||||
UsersRepo usersRepo;
|
UsersRepo usersRepo;
|
||||||
@Inject
|
@Inject
|
||||||
SessionsRepo sessionsRepo;
|
SessionsRepo sessionsRepo;
|
||||||
|
@Inject
|
||||||
|
UriInfo uriInfo;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
@@ -102,7 +105,9 @@ public class OAuthResource {
|
|||||||
return new DeviceResponse(
|
return new DeviceResponse(
|
||||||
sessionsRepo.generateAuthorizationCode(sessionId),
|
sessionsRepo.generateAuthorizationCode(sessionId),
|
||||||
sessionsRepo.generateUserCode(sessionId),
|
sessionsRepo.generateUserCode(sessionId),
|
||||||
"http://verificationuri/device-login",
|
uriInfo.getBaseUriBuilder()
|
||||||
|
.path(OAuthResource.class)
|
||||||
|
.path(OAuthResource.class, "enterDeviceCode").build(),
|
||||||
10,
|
10,
|
||||||
180
|
180
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package com.ysoft.geecon.dto;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
public record DeviceResponse(
|
public record DeviceResponse(
|
||||||
@JsonProperty("device_code") String deviceCode,
|
@JsonProperty("device_code") String deviceCode,
|
||||||
@JsonProperty("user_code") String userCode,
|
@JsonProperty("user_code") String userCode,
|
||||||
@JsonProperty("verification_uri") String verificationUri,
|
@JsonProperty("verification_uri") URI verificationUri,
|
||||||
@JsonProperty("interval") long interval,
|
@JsonProperty("interval") long interval,
|
||||||
@JsonProperty("expires_in") long expiresIn
|
@JsonProperty("expires_in") long expiresIn
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -3,84 +3,67 @@ 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.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.ClientsRepo;
|
||||||
import com.ysoft.geecon.repo.UsersRepo;
|
import com.ysoft.geecon.repo.UsersRepo;
|
||||||
|
import io.quarkus.test.common.http.TestHTTPResource;
|
||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import jakarta.inject.Inject;
|
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 org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static io.restassured.RestAssured.given;
|
import java.io.IOException;
|
||||||
import static io.restassured.http.ContentType.JSON;
|
import java.net.URI;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
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
|
@QuarkusTest
|
||||||
public class DeviceAuthGrantTest {
|
public class DeviceAuthGrantTest {
|
||||||
|
|
||||||
|
public static final OAuthClient CLIENT = new OAuthClient("deviceclient", "", null, null);
|
||||||
@Inject
|
@Inject
|
||||||
ClientsRepo clientsRepo;
|
ClientsRepo clientsRepo;
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepo usersRepo;
|
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
|
@Test
|
||||||
public void deviceAuthGrant_invalidCode() {
|
public void deviceAuthGrant_invalidCode() throws IOException {
|
||||||
given().formParam("code", "somecode").
|
DeviceCodeScreen deviceCodeScreen = new DeviceCodeScreen(deviceLoginUri);
|
||||||
when().post("/auth/device-login").
|
|
||||||
then().statusCode(404);
|
HttpStatusException exception = assertThrows(HttpStatusException.class, () -> deviceCodeScreen.enterCode("somecode"));
|
||||||
|
assertThat(exception.getStatusCode(), is(404));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deviceAuthGrant() {
|
public void deviceAuthGrant() throws IOException {
|
||||||
clientsRepo.register(new OAuthClient("myclient", "", null, null));
|
DeviceAuthorizationGrantFlow flow = new DeviceAuthorizationGrantFlow(deviceUri, CLIENT);
|
||||||
usersRepo.register(new User("bob", "password"));
|
DeviceResponse deviceResponse = flow.start();
|
||||||
|
|
||||||
DeviceResponse deviceResponse = given().
|
DeviceCodeScreen deviceCodeScreen = new DeviceCodeScreen(deviceResponse.verificationUri());
|
||||||
formParam("client_id", "myclient").
|
LoginScreen loginScreen = deviceCodeScreen.enterCode(deviceResponse.userCode());
|
||||||
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);
|
|
||||||
|
|
||||||
String deviceLogin = given().formParam("code", deviceResponse.userCode()).
|
ConsentScreen consentScreen = loginScreen.submitCorrect("bob", "password");
|
||||||
when().post("/auth/device-login").
|
consentScreen.submit();
|
||||||
then().statusCode(200)
|
|
||||||
.extract().body().asString();
|
|
||||||
|
|
||||||
String sessionId = Jsoup.parse(deviceLogin).getElementsByAttributeValue("name", "sessionId").first().attr("value");
|
flow.exchangeDeviceCode();
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,6 @@ public class LoginScreen {
|
|||||||
|
|
||||||
public LoginScreen(Document doc) {
|
public LoginScreen(Document doc) {
|
||||||
this.form = doc.expectForm("form");
|
this.form = doc.expectForm("form");
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Document submit(String username, String password) throws IOException {
|
public Document submit(String username, String password) throws IOException {
|
||||||
|
|||||||
Reference in New Issue
Block a user