diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java index 0941ecdc..2fda8a6c 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java @@ -16,8 +16,8 @@ import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; -import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client; import stirling.software.SPDF.model.User; +import stirling.software.SPDF.model.UsernameAttribute; @Slf4j public class CustomOAuth2UserService implements OAuth2UserService { @@ -41,28 +41,12 @@ public class CustomOAuth2UserService implements OAuth2UserService internalUser = userService.findByUsernameIgnoreCase(username); @@ -78,10 +62,7 @@ public class CustomOAuth2UserService implements OAuth2UserService { Set mappedAuthorities = new HashSet<>(); diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 4dfcf0ca..df54d12d 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -140,23 +140,19 @@ public class AccountWebController { case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage"; case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType"; case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse"; - case "authorization_request_not_found" -> - errorOAuth = "login.oauth2RequestNotFound"; + case "authorization_request_not_found" -> errorOAuth = "login.oauth2RequestNotFound"; case "access_denied" -> errorOAuth = "login.oauth2AccessDenied"; - case "invalid_user_info_response" -> - errorOAuth = "login.oauth2InvalidUserInfoResponse"; + case "invalid_user_info_response" -> errorOAuth = "login.oauth2InvalidUserInfoResponse"; case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest"; case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken"; case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser"; case "userIsDisabled" -> errorOAuth = "login.userIsDisabled"; case "invalid_destination" -> errorOAuth = "login.invalid_destination"; - case "relying_party_registration_not_found" -> - errorOAuth = "login.relyingPartyRegistrationNotFound"; + case "relying_party_registration_not_found" -> errorOAuth = "login.relyingPartyRegistrationNotFound"; // Valid InResponseTo was not available from the validation context, unable to // evaluate case "invalid_in_response_to" -> errorOAuth = "login.invalid_in_response_to"; - case "not_authentication_provider_found" -> - errorOAuth = "login.not_authentication_provider_found"; + case "not_authentication_provider_found" -> errorOAuth = "login.not_authentication_provider_found"; } model.addAttribute("errorOAuth", errorOAuth); diff --git a/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java b/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java new file mode 100644 index 00000000..24bccaf9 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java @@ -0,0 +1,21 @@ +package stirling.software.SPDF.model; + +import lombok.Getter; + +@Getter +public enum UsernameAttribute { + NAME("name"), + EMAIL("email"), + GIVEN_NAME("given_name"), + PREFERRED_NAME("preferred_name"), + PREFERRED_USERNAME("preferred_username"), + LOGIN("login"), + FAMILY_NAME("family_name"), + NICKNAME("nickname"); + + private final String name; + + UsernameAttribute(final String name) { + this.name = name; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java new file mode 100644 index 00000000..0bf06ee2 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java @@ -0,0 +1,7 @@ +package stirling.software.SPDF.model.exception; + +public class UnsupportedUsernameAttribute extends RuntimeException { + public UnsupportedUsernameAttribute(String message) { + super(message); + } +} diff --git a/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java b/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java index 5b2aa65c..e2b368a3 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; @NoArgsConstructor public class GitHubProvider extends Provider { @@ -15,7 +16,10 @@ public class GitHubProvider extends Provider { private static final String USER_INFO_URI = "https://api.github.com/user"; public GitHubProvider( - String clientId, String clientSecret, Collection scopes, String useAsUsername) { + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername) { super( null, NAME, @@ -23,7 +27,7 @@ public class GitHubProvider extends Provider { clientId, clientSecret, scopes, - useAsUsername != null ? useAsUsername : "login", + useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN, AUTHORIZATION_URI, TOKEN_URI, USER_INFO_URI); diff --git a/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java b/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java index 26557965..fb3388f8 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; @NoArgsConstructor public class GoogleProvider extends Provider { @@ -16,7 +17,10 @@ public class GoogleProvider extends Provider { "https://www.googleapis.com/oauth2/v3/userinfo?alt=json"; public GoogleProvider( - String clientId, String clientSecret, Collection scopes, String useAsUsername) { + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername) { super( null, NAME, diff --git a/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java b/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java index 6649c2fa..d989e5f8 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; @NoArgsConstructor public class KeycloakProvider extends Provider { @@ -16,7 +17,7 @@ public class KeycloakProvider extends Provider { String clientId, String clientSecret, Collection scopes, - String useAsUsername) { + UsernameAttribute useAsUsername) { super( issuer, NAME, diff --git a/src/main/java/stirling/software/SPDF/model/provider/Provider.java b/src/main/java/stirling/software/SPDF/model/provider/Provider.java index 903ec6e9..5ba1ba2c 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/Provider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/Provider.java @@ -1,6 +1,6 @@ package stirling.software.SPDF.model.provider; -import static stirling.software.SPDF.utils.validation.Validator.isStringEmpty; +import static stirling.software.SPDF.model.UsernameAttribute.EMAIL; import java.util.ArrayList; import java.util.Arrays; @@ -9,6 +9,8 @@ import java.util.stream.Collectors; import lombok.Data; import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; +import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute; @Data @NoArgsConstructor @@ -20,7 +22,7 @@ public class Provider { private String clientId; private String clientSecret; private Collection scopes; - private String useAsUsername; + private UsernameAttribute useAsUsername; private String authorizationUri; private String tokenUri; private String userInfoUri; @@ -32,7 +34,7 @@ public class Provider { String clientId, String clientSecret, Collection scopes, - String useAsUsername, + UsernameAttribute useAsUsername, String authorizationUri, String tokenUri, String userInfoUri) { @@ -42,7 +44,8 @@ public class Provider { this.clientId = clientId; this.clientSecret = clientSecret; this.scopes = scopes == null ? new ArrayList<>() : scopes; - this.useAsUsername = isStringEmpty(useAsUsername) ? "email" : useAsUsername; + this.useAsUsername = + useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL; this.authorizationUri = authorizationUri; this.tokenUri = tokenUri; this.userInfoUri = userInfoUri; @@ -55,6 +58,69 @@ public class Provider { } } + private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (name) { + case "google" -> { + return validateGoogleUsernameAttribute(usernameAttribute); + } + case "github" -> { + return validateGitHubUsernameAttribute(usernameAttribute); + } + case "keycloak" -> { + return validateKeycloakUsernameAttribute(usernameAttribute); + } + default -> { + return usernameAttribute; + } + } + } + + private UsernameAttribute validateKeycloakUsernameAttribute( + UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case EMAIL, PREFERRED_NAME -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + "The attribute " + + usernameAttribute + + "is not supported for " + + clientName + + "."); + } + } + + private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case EMAIL, NAME, GIVEN_NAME, PREFERRED_NAME -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + "The attribute " + + usernameAttribute + + "is not supported for " + + clientName + + "."); + } + } + + private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case EMAIL, NAME, LOGIN -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + "The attribute " + + usernameAttribute + + "is not supported for " + + clientName + + "."); + } + } + @Override public String toString() { return "Provider [name=" diff --git a/src/main/java/stirling/software/SPDF/utils/validation/Validator.java b/src/main/java/stirling/software/SPDF/utils/validation/Validator.java index 0a184235..83b90685 100644 --- a/src/main/java/stirling/software/SPDF/utils/validation/Validator.java +++ b/src/main/java/stirling/software/SPDF/utils/validation/Validator.java @@ -23,10 +23,6 @@ public class Validator { return false; } - if (isStringEmpty(provider.getUseAsUsername())) { - return false; - } - return true; } diff --git a/src/test/java/stirling/software/SPDF/utils/validation/ValidatorTest.java b/src/test/java/stirling/software/SPDF/utils/validation/ValidatorTest.java index b9860432..98635198 100644 --- a/src/test/java/stirling/software/SPDF/utils/validation/ValidatorTest.java +++ b/src/test/java/stirling/software/SPDF/utils/validation/ValidatorTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.junit.jupiter.MockitoExtension; +import stirling.software.SPDF.model.UsernameAttribute; import stirling.software.SPDF.model.provider.GitHubProvider; import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.KeycloakProvider; @@ -28,7 +29,7 @@ class ValidatorTest { when(provider.getClientId()).thenReturn("clientId"); when(provider.getClientSecret()).thenReturn("clientSecret"); when(provider.getScopes()).thenReturn(List.of("read:user")); - when(provider.getUseAsUsername()).thenReturn("email"); + when(provider.getUseAsUsername()).thenReturn(UsernameAttribute.EMAIL); assertTrue(Validator.validateProvider(provider)); } @@ -41,9 +42,9 @@ class ValidatorTest { public static Stream providerParams() { Provider generic = null; - var google = new GoogleProvider(null, "clientSecret", List.of("scope"), "email"); - var github = new GitHubProvider("clientId", "", List.of("scope"), "login"); - var keycloak = new KeycloakProvider("issuer", "clientId", "clientSecret", List.of("scope"), "email"); + var google = new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL); + var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN); + var keycloak = new KeycloakProvider("issuer", "clientId", "clientSecret", List.of("scope"), UsernameAttribute.EMAIL); keycloak.setUseAsUsername(null);