From 38403ff828877e5992d718977208a4b3791ff953 Mon Sep 17 00:00:00 2001 From: Dusan Jakub Date: Fri, 15 Sep 2023 14:06:27 +0200 Subject: [PATCH] Token Endpoint --- pom.xml | 4 ++ .../java/com/ysoft/geecon/OAuthResource.java | 48 ++++++++++++-- .../ysoft/geecon/dto/AccessTokenResponse.java | 4 +- .../java/com/ysoft/geecon/dto/AuthParams.java | 10 +-- .../geecon/dto/AuthorizationSession.java | 4 ++ .../com/ysoft/geecon/dto/OAuthClient.java | 6 ++ .../com/ysoft/geecon/dto/TokenParams.java | 65 +++++++++++++++++++ .../com/ysoft/geecon/repo/ClientsRepo.java | 1 + 8 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/ysoft/geecon/dto/TokenParams.java diff --git a/pom.xml b/pom.xml index 5549ff8..fba2dad 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,10 @@ io.quarkus quarkus-resteasy-reactive-qute + + io.quarkus + quarkus-resteasy-reactive-jackson + io.quarkus quarkus-junit5 diff --git a/src/main/java/com/ysoft/geecon/OAuthResource.java b/src/main/java/com/ysoft/geecon/OAuthResource.java index d811871..62a022b 100644 --- a/src/main/java/com/ysoft/geecon/OAuthResource.java +++ b/src/main/java/com/ysoft/geecon/OAuthResource.java @@ -1,10 +1,9 @@ package com.ysoft.geecon; -import com.ysoft.geecon.dto.AuthParams; -import com.ysoft.geecon.dto.OAuthClient; -import com.ysoft.geecon.dto.User; +import com.ysoft.geecon.dto.*; import com.ysoft.geecon.error.OAuthException; import com.ysoft.geecon.repo.ClientsRepo; +import com.ysoft.geecon.repo.SecureRandomStrings; import com.ysoft.geecon.repo.SessionsRepo; import com.ysoft.geecon.repo.UsersRepo; import io.quarkus.qute.CheckedTemplate; @@ -17,6 +16,7 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.util.List; +import java.util.Optional; @Path("/auth") public class OAuthResource { @@ -40,7 +40,7 @@ public class OAuthResource { public TemplateInstance get(AuthParams params) { var client = validateClient(params); String sessionId = sessionsRepo.newAuthorizationSession(params, client); - return Templates.login(params.getLoginHint(), sessionId, ""); + return Templates.login(params.getLoginHint(), sessionId, ""); } @POST @@ -75,7 +75,34 @@ public class OAuthResource { .queryParam("state", params.getState()) .build()) .build(); - } + } + + @POST + @Path("/token") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public AccessTokenResponse token(TokenParams params) { + return switch (params.getGrantType()) { + case "authorization_code" -> redeemAuthorizationCode(params); + default -> throw new OAuthException("Unsupported grant type"); + }; + } + + private AccessTokenResponse redeemAuthorizationCode(TokenParams params) { + validateClient(params); + var session = sessionsRepo.redeemAuthorizationCode(params.getCode()) + .orElseThrow(() -> new OAuthException("Invalid code")); + + String idToken = null; + + return new AccessTokenResponse("Bearer", + 8400, + SecureRandomStrings.alphanumeric(50), + session.scope(), + SecureRandomStrings.alphanumeric(50), + idToken + ); + } private User validateUser(String username, String password) { return usersRepo.getUser(username) @@ -95,6 +122,17 @@ public class OAuthResource { return client; } + private OAuthClient validateClient(TokenParams params) { + var client = clientsRepo.getClient(params.getClientId()) + .orElseThrow(() -> new RuntimeException("Not a valid client")); + if (!client.validateRedirectUri(params.getRedirectUri())) { + throw new RuntimeException("Invalid redirect URI"); + } + if (!client.validateSecret(params.getClientSecret())) { + throw new RuntimeException("Invalid secret"); + } + return client; + } } diff --git a/src/main/java/com/ysoft/geecon/dto/AccessTokenResponse.java b/src/main/java/com/ysoft/geecon/dto/AccessTokenResponse.java index 8b7ce51..f74d636 100644 --- a/src/main/java/com/ysoft/geecon/dto/AccessTokenResponse.java +++ b/src/main/java/com/ysoft/geecon/dto/AccessTokenResponse.java @@ -1,4 +1,4 @@ package com.ysoft.geecon.dto; -public record AccessTokenResponse(String token, String scope, String idToken, long expiresIn) { -} +public record AccessTokenResponse(String tokenType, long expiresIn, String accessToken, String scope, String refreshToken, String idToken) { +} \ No newline at end of file diff --git a/src/main/java/com/ysoft/geecon/dto/AuthParams.java b/src/main/java/com/ysoft/geecon/dto/AuthParams.java index 0017160..d2099c4 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthParams.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthParams.java @@ -6,14 +6,10 @@ import java.util.Arrays; import java.util.List; public class AuthParams { - public enum ResponseType { - code - } - @RestQuery("login_hint") String loginHint; @RestQuery("response_type") - ResponseType responseType; + String responseType; @RestQuery("client_id") String clientId; @RestQuery("redirect_uri") @@ -31,11 +27,11 @@ public class AuthParams { this.loginHint = loginHint; } - public ResponseType getResponseType() { + public String getResponseType() { return responseType; } - public void setResponseType(ResponseType responseType) { + public void setResponseType(String responseType) { this.responseType = responseType; } diff --git a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java index 0924fec..547bb43 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java @@ -14,4 +14,8 @@ public record AuthorizationSession(AuthParams params, OAuthClient client, User u public AuthorizationSession withScopes(List acceptedScopes) { return new AuthorizationSession(params, client, user, acceptedScopes); } + + public String scope() { + return acceptedScopes == null ? null : String.join(" ", acceptedScopes); + } } diff --git a/src/main/java/com/ysoft/geecon/dto/OAuthClient.java b/src/main/java/com/ysoft/geecon/dto/OAuthClient.java index 88f0eee..c025309 100644 --- a/src/main/java/com/ysoft/geecon/dto/OAuthClient.java +++ b/src/main/java/com/ysoft/geecon/dto/OAuthClient.java @@ -1,7 +1,13 @@ package com.ysoft.geecon.dto; +import java.util.Objects; + public record OAuthClient(String clientId, String description, String clientSecret, String redirectUri) { public boolean validateRedirectUri(String redirectUri) { return this.redirectUri != null && this.redirectUri.equals(redirectUri); } + + public boolean validateSecret(String clientSecret) { + return Objects.equals(clientSecret, this.clientSecret); + } } diff --git a/src/main/java/com/ysoft/geecon/dto/TokenParams.java b/src/main/java/com/ysoft/geecon/dto/TokenParams.java new file mode 100644 index 0000000..a1a2e04 --- /dev/null +++ b/src/main/java/com/ysoft/geecon/dto/TokenParams.java @@ -0,0 +1,65 @@ +package com.ysoft.geecon.dto; + +import jakarta.ws.rs.FormParam; +import org.jboss.resteasy.reactive.RestQuery; + +import java.util.Arrays; +import java.util.List; + +public class TokenParams { + + @FormParam("grant_type") + private String grantType; + + @FormParam("client_id") + private String clientId; + + @FormParam("client_secret") + private String clientSecret; + + @FormParam("redirect_uri") + private String redirectUri; + + @FormParam("code") + private String code; + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/main/java/com/ysoft/geecon/repo/ClientsRepo.java b/src/main/java/com/ysoft/geecon/repo/ClientsRepo.java index 88d33f7..6d8ab0a 100644 --- a/src/main/java/com/ysoft/geecon/repo/ClientsRepo.java +++ b/src/main/java/com/ysoft/geecon/repo/ClientsRepo.java @@ -14,6 +14,7 @@ public class ClientsRepo { public ClientsRepo() { register(new OAuthClient("my-public-client", "Example public client", null, "https://localhost:8888/oauth_success")); + register(new OAuthClient("oauthdebugger", "Example public client", null, "https://oauthdebugger.com/debug")); } public Optional getClient(String clientId) {