Device Auth. Grant - redden device token, get token

This commit is contained in:
Dusan Jakub
2023-09-18 14:17:37 +02:00
parent e464146476
commit 2b2616cbe7
4 changed files with 45 additions and 14 deletions

View File

@@ -3,7 +3,6 @@ package com.ysoft.geecon;
import com.ysoft.geecon.dto.*; import com.ysoft.geecon.dto.*;
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.SecureRandomStrings;
import com.ysoft.geecon.repo.SessionsRepo; import com.ysoft.geecon.repo.SessionsRepo;
import com.ysoft.geecon.repo.UsersRepo; import com.ysoft.geecon.repo.UsersRepo;
import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.CheckedTemplate;
@@ -116,6 +115,7 @@ public class OAuthResource {
public AccessTokenResponse token(TokenParams params) { public AccessTokenResponse token(TokenParams params) {
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);
default -> throw new OAuthException("Unsupported grant type"); default -> throw new OAuthException("Unsupported grant type");
}; };
} }
@@ -143,22 +143,25 @@ public class OAuthResource {
} }
private AccessTokenResponse redeemAuthorizationCode(TokenParams params) { private AccessTokenResponse redeemAuthorizationCode(TokenParams params) {
validateClient(params);
var session = sessionsRepo.redeemAuthorizationCode(params.getCode()) var session = sessionsRepo.redeemAuthorizationCode(params.getCode())
.orElseThrow(() -> new OAuthException("Invalid code")); .orElseThrow(() -> new OAuthException("Invalid code"));
validateClient(params, session);
if (!session.validateCodeChallenge(params.getCodeVerifier())) { if (!session.validateCodeChallenge(params.getCodeVerifier())) {
throw new OAuthException("Invalid code verifier"); throw new OAuthException("Invalid code verifier");
} }
return session.tokens();
}
String idToken = null; private AccessTokenResponse redeemDeviceCode(TokenParams params) {
var session = sessionsRepo.getByAuthorizationCode(params.getDeviceCode())
return new AccessTokenResponse("Bearer", .orElseThrow(() -> new OAuthException("Invalid code"));
8400, validateClient(params, session);
SecureRandomStrings.alphanumeric(50), if (session.tokens() != null) {
session.scope(), sessionsRepo.redeemAuthorizationCode(params.getDeviceCode());
SecureRandomStrings.alphanumeric(50), return session.tokens();
idToken } else {
); throw new OAuthException("Authorization pending");
}
} }
private User validateUser(String username, String password) { private User validateUser(String username, String password) {
@@ -179,10 +182,10 @@ public class OAuthResource {
return client; return client;
} }
private OAuthClient validateClient(TokenParams params) { 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 RuntimeException("Not a valid client"));
if (!client.validateRedirectUri(params.getRedirectUri())) { if (!session.validateRedirectUri(params.getRedirectUri())) {
throw new RuntimeException("Invalid redirect URI"); throw new RuntimeException("Invalid redirect URI");
} }
if (!client.validateSecret(params.getClientSecret())) { if (!client.validateSecret(params.getClientSecret())) {

View File

@@ -3,6 +3,7 @@ package com.ysoft.geecon.dto;
import com.ysoft.geecon.repo.SecureRandomStrings; import com.ysoft.geecon.repo.SecureRandomStrings;
import java.util.List; import java.util.List;
import java.util.Objects;
public record AuthorizationSession(String sessionId, public record AuthorizationSession(String sessionId,
AuthParams params, AuthParams params,
@@ -48,4 +49,7 @@ public record AuthorizationSession(String sessionId,
return Pkce.validate(params.codeChallengeMethod, params.codeChallenge, codeVerifier); return Pkce.validate(params.codeChallengeMethod, params.codeChallenge, codeVerifier);
} }
public boolean validateRedirectUri(String redirectUri) {
return Objects.equals(params.redirectUri, redirectUri);
}
} }

View File

@@ -22,6 +22,9 @@ public class TokenParams {
@FormParam("code_verifier") @FormParam("code_verifier")
private String codeVerifier; private String codeVerifier;
@FormParam("device_code")
private String deviceCode;
public String getGrantType() { public String getGrantType() {
return grantType; return grantType;
} }
@@ -69,4 +72,12 @@ public class TokenParams {
public void setCodeVerifier(String codeVerifier) { public void setCodeVerifier(String codeVerifier) {
this.codeVerifier = codeVerifier; this.codeVerifier = codeVerifier;
} }
public String getDeviceCode() {
return deviceCode;
}
public void setDeviceCode(String deviceCode) {
this.deviceCode = deviceCode;
}
} }

View File

@@ -68,6 +68,19 @@ public class DeviceAuthGrantTest {
when(). when().
post("auth/consent") post("auth/consent")
.then().statusCode(200); .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()));
}
} }