diff --git a/jpro-auth/core/src/main/java/module-info.java b/jpro-auth/core/src/main/java/module-info.java index a53fbe73..cf09765a 100644 --- a/jpro-auth/core/src/main/java/module-info.java +++ b/jpro-auth/core/src/main/java/module-info.java @@ -20,6 +20,7 @@ exports one.jpro.platform.auth.core; exports one.jpro.platform.auth.core.api; exports one.jpro.platform.auth.core.authentication; + exports one.jpro.platform.auth.core.basic; exports one.jpro.platform.auth.core.jwt; exports one.jpro.platform.auth.core.oauth2; exports one.jpro.platform.auth.core.oauth2.provider; diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/AuthAPI.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/AuthAPI.java index 19e33aca..16bf80b0 100644 --- a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/AuthAPI.java +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/AuthAPI.java @@ -9,6 +9,15 @@ */ public interface AuthAPI { + /** + * Configure and create a basic (username and password) authentication provider. + * + * @return fluent style api. + */ + static FluentBasicAuth basicAuth() { + return new FluentBasicAuthAPI(); + } + /** * Configure and create a Google authentication provider. * diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuth.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuth.java new file mode 100644 index 00000000..7439ffc0 --- /dev/null +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuth.java @@ -0,0 +1,45 @@ +package one.jpro.platform.auth.core.api; + +import one.jpro.platform.auth.core.basic.BasicAuthenticationProvider; + +import java.util.Map; +import java.util.Set; + +/** + * Fluent Basic (username and password) Authentication interface. + * + * @author Besmir Beqiri + */ +public interface FluentBasicAuth { + + /** + * Set the roles. + * + * @param roles the roles + * @return self + */ + FluentBasicAuth roles(Set roles); + + /** + * Set the attributes. + * + * @param attributes the attributes + * @return self + */ + FluentBasicAuth attributes(Map attributes); + + /** + * Set the authorization path. + * + * @param authorizationPath the authorization path + * @return self + */ + FluentBasicAuth authorizationPath(String authorizationPath); + + /** + * Create a simple authentication provider. + * + * @return a {@link BasicAuthenticationProvider} instance. + */ + BasicAuthenticationProvider create(); +} diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuthAPI.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuthAPI.java new file mode 100644 index 00000000..8e7135dc --- /dev/null +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/api/FluentBasicAuthAPI.java @@ -0,0 +1,43 @@ +package one.jpro.platform.auth.core.api; + +import one.jpro.platform.auth.core.basic.BasicAuthenticationProvider; + +import java.util.Map; +import java.util.Set; + +/** + * Fluent Basic (username and password) Authentication API. + * + * @author Besmir Beqiri + */ +public class FluentBasicAuthAPI implements FluentBasicAuth { + + private Set roles; + private Map attributes; + private String authorizationPath = BasicAuthenticationProvider.DEFAULT_AUTHORIZATION_PATH; + + @Override + public FluentBasicAuth roles(Set roles) { + this.roles = roles; + return this; + } + + @Override + public FluentBasicAuth attributes(Map attributes) { + this.attributes = attributes; + return this; + } + + @Override + public FluentBasicAuth authorizationPath(String authorizationPath) { + this.authorizationPath = authorizationPath; + return this; + } + + @Override + public BasicAuthenticationProvider create() { + final var basicAuthProvider = new BasicAuthenticationProvider(roles, attributes); + basicAuthProvider.setAuthorizationPath(authorizationPath); + return basicAuthProvider; + } +} diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/Authentication.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/Authentication.java index 27fc4936..698af11a 100755 --- a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/Authentication.java +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/Authentication.java @@ -51,23 +51,37 @@ default JSONObject toJSON() { return json; } + /** + * Build an {@link Authentication} instance for the user. + * + * @param username User's name + * @return an {@link Authentication} object + */ @NotNull static Authentication create(@NotNull String username) { - return Authentication.create(username, null, null); + return new User(username, null, null); } - static Authentication create(@NotNull String username, - @NotNull Set roles) { + /** + * Build an {@link Authentication} instance for the user. + * + * @param username User's name + * @param roles User's roles + * @return an {@link Authentication} object + */ + @NotNull + static Authentication create(@NotNull String username, + @NotNull Set roles) { Objects.requireNonNull(roles, "User's roles are null."); return new User(username, roles, null); } /** - * Build an {@link Authentication} instance foe the user. + * Build an {@link Authentication} instance for the user. * - * @param username User's name + * @param username User's name * @param attributes User's attributes - * @return An {@link Authentication} for the user + * @return an {@link Authentication} object */ @NotNull static Authentication create(@NotNull String username, @@ -79,10 +93,10 @@ static Authentication create(@NotNull String username, /** * Builds an {@link Authentication} instance for the user. * - * @param username User's name - * @param roles User's roles + * @param username User's name + * @param roles User's roles * @param attributes User's attributes - * @return An {@link Authentication} for the user + * @return an {@link Authentication} object */ @NotNull static Authentication create(@NotNull String username, @@ -92,10 +106,10 @@ static Authentication create(@NotNull String username, } /** - * Builds an {@link Authentication} instance for the user from a {@link JSONObject}. + * Builds an {@link User} instance for the user from a {@link JSONObject}. * * @param json a {@link JSONObject} containing user's data. - * @return An {@link Authentication} for the user + * @return an {@link User} object */ @NotNull static User create(@NotNull JSONObject json) { diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/BasicAuthenticationProvider.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/BasicAuthenticationProvider.java new file mode 100644 index 00000000..21faa7b0 --- /dev/null +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/BasicAuthenticationProvider.java @@ -0,0 +1,134 @@ +package one.jpro.platform.auth.core.basic; + +import one.jpro.platform.auth.core.authentication.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +/** + * The {@code BasicAuthenticationProvider} class implements the {@code AuthenticationProvider} interface + * to provide basic authentication using username and password credentials. + * + * @author Besmir Beqiri + */ +public class BasicAuthenticationProvider implements AuthenticationProvider { + + private static final Logger logger = LoggerFactory.getLogger(BasicAuthenticationProvider.class); + + public static final String DEFAULT_AUTHORIZATION_PATH = "/auth/basic"; + + @NotNull + private String authorizationPath = DEFAULT_AUTHORIZATION_PATH; + @Nullable + private Set roles; + @Nullable + private Map attributes; + + /** + * Constructs a new {@code BasicAuthenticationProvider} with specified roles and attributes. + * + * @param roles the set of roles to be associated with the authenticated user, may be {@code null}. + * @param attributes the map of attributes to be associated with the authenticated user, may be {@code null}. + */ + public BasicAuthenticationProvider(@Nullable final Set roles, + @Nullable final Map attributes) { + this.roles = roles; + this.attributes = attributes; + } + + /** + * Authenticates the user based on the provided {@code UsernamePasswordCredentials}. + * + * @param credentials the credentials containing the username and password + * @return a {@code CompletableFuture} that, when completed, provides the authenticated {@code User}. + * @throws CredentialValidationException if the credentials are not valid + */ + @NotNull + @Override + public CompletableFuture authenticate(@NotNull final UsernamePasswordCredentials credentials) + throws CredentialValidationException { + try { + credentials.validate(null); + } catch (CredentialValidationException ex) { + logger.error("Username and password credentials not valid", ex); + return CompletableFuture.failedFuture(ex); + } + + JSONObject userJSON = new JSONObject(); + userJSON.put(User.KEY_NAME, credentials.getUsername()); + userJSON.put(User.KEY_ROLES, new JSONArray(roles)); + + JSONObject authJSON = new JSONObject(); + authJSON.put("password", credentials.getPassword()); + userJSON.put(User.KEY_ATTRIBUTES, new JSONObject(attributes).put("auth", authJSON)); + + final User user = Authentication.create(userJSON); + return CompletableFuture.completedFuture(user); + } + + /** + * Gets the authorization path URI for basic authentication. + * This is the URI path that the users will be redirected to if they need to be authenticated. + * + * @return the authorization path string + */ + @NotNull + public String getAuthorizationPath() { + return authorizationPath; + } + + /** + * Sets the authorization path for basic authentication. + * This is the URI path that the users will be redirected to if they need to be authenticated. + * + * @param authorizationPath the authorization path string + */ + public void setAuthorizationPath(@NotNull String authorizationPath) { + this.authorizationPath = authorizationPath; + } + + /** + * Gets the set of roles associated with this authentication provider. + * + * @return The set of roles, may be {@code null}. + */ + @Nullable + public Set getRoles() { + return roles; + } + + /** + * Sets the roles to be associated with this authentication provider. + * + * @param roles The set of roles, may be {@code null}. + */ + public void setRoles(@Nullable Set roles) { + this.roles = roles; + } + + /** + * Gets the attributes associated with this authentication provider. + * + * @return The attributes, may be {@code null}. + */ + @Nullable + public Map getAttributes() { + return attributes; + } + + /** + * Sets the attributes to be associated with this authentication provider. + * + * @param attributes The map of attributes, may be {@code null}. + */ + public void setAttributes(@Nullable Map attributes) { + this.attributes = attributes; + } +} diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/UsernamePasswordCredentials.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/UsernamePasswordCredentials.java similarity index 71% rename from jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/UsernamePasswordCredentials.java rename to jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/UsernamePasswordCredentials.java index 66207a17..23f8f130 100755 --- a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/authentication/UsernamePasswordCredentials.java +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/basic/UsernamePasswordCredentials.java @@ -1,7 +1,10 @@ -package one.jpro.platform.auth.core.authentication; +package one.jpro.platform.auth.core.basic; +import one.jpro.platform.auth.core.authentication.CredentialValidationException; +import one.jpro.platform.auth.core.authentication.Credentials; import one.jpro.platform.auth.core.utils.AuthUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.json.JSONObject; import java.nio.charset.StandardCharsets; @@ -18,24 +21,31 @@ public class UsernamePasswordCredentials implements Credentials { private static final Base64.Encoder BASE64_ENCODER = AuthUtils.BASE64_ENCODER; - @NotNull + @Nullable private String username; - @NotNull + @Nullable private String password; /** * Default constructor. + */ + public UsernamePasswordCredentials() { + } + + /** + * Constructor with username and password. * - * @param username User's name. - * @param password User's password. + * @param username User's name + * @param password User's password */ - public UsernamePasswordCredentials(@NotNull final String username, @NotNull final String password) { + public UsernamePasswordCredentials(@NotNull String username, @NotNull String password) { this.username = username; this.password = password; } - public @NotNull String getUsername() { + @Nullable + public String getUsername() { return username; } @@ -43,7 +53,8 @@ public void setUsername(@NotNull String username) { this.username = username; } - public @NotNull String getPassword() { + @Nullable + public String getPassword() { return password; } @@ -54,12 +65,12 @@ public void setPassword(@NotNull String password) { @Override public void validate(V arg) throws CredentialValidationException { if (username == null || username.isBlank()) { - throw new CredentialValidationException("username cannot be null or blank"); + throw new CredentialValidationException("Username cannot be null or blank"); } // passwords are allowed to be empty // for example this is used by basic auth if (password == null) { - throw new CredentialValidationException("password cannot be null"); + throw new CredentialValidationException("Password cannot be null"); } } @@ -100,8 +111,8 @@ public String toHttpAuthorization() { @Override public JSONObject toJSON() { JSONObject json = new JSONObject(); - Optional.of(getUsername()).ifPresent(username -> json.put("username", username)); - Optional.of(getPassword()).ifPresent(password -> json.put("password", password)); + Optional.ofNullable(getUsername()).ifPresent(username -> json.put("username", username)); + Optional.ofNullable(getPassword()).ifPresent(password -> json.put("password", password)); return json; } } diff --git a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/oauth2/OAuth2AuthenticationProvider.java b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/oauth2/OAuth2AuthenticationProvider.java index a87e32cf..95f7808d 100755 --- a/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/oauth2/OAuth2AuthenticationProvider.java +++ b/jpro-auth/core/src/main/java/one/jpro/platform/auth/core/oauth2/OAuth2AuthenticationProvider.java @@ -12,6 +12,7 @@ import com.jpro.webapi.WebAPI; import javafx.stage.Stage; import one.jpro.platform.auth.core.authentication.*; +import one.jpro.platform.auth.core.basic.UsernamePasswordCredentials; import one.jpro.platform.auth.core.http.HttpServer; import one.jpro.platform.auth.core.jwt.JWTOptions; import one.jpro.platform.auth.core.jwt.TokenCredentials; diff --git a/jpro-auth/core/src/test/java/one/jpro/platform/auth/core/test/UsernamePasswordCredentialsTest.java b/jpro-auth/core/src/test/java/one/jpro/platform/auth/core/test/UsernamePasswordCredentialsTest.java index 1568aa02..1e00bd48 100644 --- a/jpro-auth/core/src/test/java/one/jpro/platform/auth/core/test/UsernamePasswordCredentialsTest.java +++ b/jpro-auth/core/src/test/java/one/jpro/platform/auth/core/test/UsernamePasswordCredentialsTest.java @@ -1,7 +1,7 @@ package one.jpro.platform.auth.core.test; import one.jpro.platform.auth.core.authentication.CredentialValidationException; -import one.jpro.platform.auth.core.authentication.UsernamePasswordCredentials; +import one.jpro.platform.auth.core.basic.UsernamePasswordCredentials; import org.json.JSONObject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;