From 4449b9228485ae70158fa9ca2b6fedcd4774ad12 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:47:18 +0100 Subject: [PATCH 001/107] [broker-67] Add authentications modules and initial class structure --- .../service/acl/builder/AllOfBuilder.groovy | 2 +- .../config/MqttBrokerSpringConfig.java | 32 +++++++-- authentication/build.gradle | 15 +++++ .../mqtt/service/auth/AuthMechanism.java | 4 ++ .../service/auth/AuthenticationService.java | 8 +++ .../mqtt/service/auth}/CredentialSource.java | 2 +- .../auth/DefaultAuthenticationService.java | 31 +++++++++ .../mqtt/service/auth/package-info.java | 4 ++ .../auth/provider/AnonymousProvider.java | 17 +++++ .../auth/provider/AuthenticationProvider.java | 11 ++++ .../service/auth/provider/DenyProvider.java | 17 +++++ .../auth/provider/ProviderResponse.java | 6 ++ basic-authentication/build.gradle | 19 ++++++ .../PasswordBasedAuthenticationProvider.java | 28 ++++++++ .../mqtt/service/auth/package-info.java | 4 ++ .../source}/AbstractCredentialSource.java | 6 +- .../auth/source/DbCredentialsSource.java | 65 +++++++++++++++++++ .../auth/source}/FileCredentialsSource.java | 2 +- .../service/auth/source/package-info.java | 4 ++ core-service/build.gradle | 3 +- .../mqtt/service/AuthenticationService.java | 7 -- .../impl/SimpleAuthenticationService.java | 22 ------- .../impl/ConnectInMqttInMessageHandler.java | 4 +- .../mqtt/model/message/AuthRequest.java | 12 ++++ .../message/in/ConnectMqttInMessage.java | 3 +- settings.gradle | 2 + 26 files changed, 284 insertions(+), 46 deletions(-) create mode 100644 authentication/build.gradle create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java rename {core-service/src/main/java/javasabr/mqtt/service => authentication/src/main/java/javasabr/mqtt/service/auth}/CredentialSource.java (81%) create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java create mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java create mode 100644 basic-authentication/build.gradle create mode 100644 basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java create mode 100644 basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java rename {core-service/src/main/java/javasabr/mqtt/service/impl => basic-authentication/src/main/java/javasabr/mqtt/service/auth/source}/AbstractCredentialSource.java (90%) create mode 100644 basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java rename {core-service/src/main/java/javasabr/mqtt/service/impl => basic-authentication/src/main/java/javasabr/mqtt/service/auth/source}/FileCredentialsSource.java (97%) create mode 100644 basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java delete mode 100644 core-service/src/main/java/javasabr/mqtt/service/AuthenticationService.java delete mode 100644 core-service/src/main/java/javasabr/mqtt/service/impl/SimpleAuthenticationService.java create mode 100644 model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy index 94f42e40..2ab61ff6 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy @@ -12,7 +12,7 @@ class AllOfBuilder extends ConditionBuilder { USER_NAME, CLIENT_ID, IP_ADDRESS } - private final Set alreadySetIdentities = new HashSet<>() + private final Set alreadySetIdentities = EnumSet.noneOf(Identity.class) @Override ConditionBuilder userName(ValueMatcher... userNames) { diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index eb4c4470..5ed4ea51 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -2,6 +2,10 @@ import java.net.InetSocketAddress; import java.util.Collection; +import java.util.List; +import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.mqtt.service.auth.AuthenticationService; +import javasabr.mqtt.service.auth.DefaultAuthenticationService; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -10,15 +14,15 @@ import javasabr.mqtt.network.handler.NetworkMqttUserReleaseHandler; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.user.NetworkMqttUserFactory; -import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; -import javasabr.mqtt.service.CredentialSource; +import javasabr.mqtt.service.auth.CredentialSource; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; +import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; import javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; @@ -27,10 +31,9 @@ import javasabr.mqtt.service.impl.DefaultPublishReceivingService; import javasabr.mqtt.service.impl.DefaultTopicService; import javasabr.mqtt.service.impl.ExternalNetworkMqttUserFactory; -import javasabr.mqtt.service.impl.FileCredentialsSource; +import javasabr.mqtt.service.auth.source.FileCredentialsSource; import javasabr.mqtt.service.impl.InMemoryClientIdRegistry; import javasabr.mqtt.service.impl.InMemorySubscriptionService; -import javasabr.mqtt.service.impl.SimpleAuthenticationService; import javasabr.mqtt.service.message.handler.MqttInMessageHandler; import javasabr.mqtt.service.message.handler.impl.ConnectInMqttInMessageHandler; import javasabr.mqtt.service.message.handler.impl.DisconnectMqttInMessageHandler; @@ -54,6 +57,7 @@ import javasabr.mqtt.service.publish.handler.impl.Qos2MqttPublishOutMessageHandler; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.mqtt.service.session.impl.InMemoryMqttSessionService; +import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.network.NetworkFactory; import javasabr.rlib.network.ServerNetworkConfig; import javasabr.rlib.network.server.ServerNetwork; @@ -90,11 +94,25 @@ CredentialSource credentialSource( return new FileCredentialsSource(fileName); } + @Bean + AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { + return new PasswordBasedAuthenticationProvider(credentialSource, "basic"); + } + @Bean AuthenticationService authenticationService( - CredentialSource credentialSource, - @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth) { - return new SimpleAuthenticationService(credentialSource, allowAnonymousAuth); + List credentialSource, + @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth) { + String defaultProviderName = "basic"; + var authenticationProviders = DictionaryFactory.mutableRefToRefDictionary( + String.class, + AuthenticationProvider.class); + credentialSource.forEach(value -> authenticationProviders.put(value.getAuthMethodName(), value)); + AuthenticationProvider defaultProvider = authenticationProviders.get(defaultProviderName); + if (defaultProvider == null) { + throw new IllegalArgumentException("%s authenticator provider not found".formatted(defaultProviderName)); + } + return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); } @Bean diff --git a/authentication/build.gradle b/authentication/build.gradle new file mode 100644 index 00000000..17b82b85 --- /dev/null +++ b/authentication/build.gradle @@ -0,0 +1,15 @@ +plugins { + id("java-library") + id("configure-java") + id("groovy") +} + +description = "Authentication service interface" + +dependencies { + api projects.base + api projects.model + + testImplementation projects.testSupport + testFixturesApi projects.testSupport +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java new file mode 100644 index 00000000..17f29336 --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java @@ -0,0 +1,4 @@ +package javasabr.mqtt.service.auth; + +public enum AuthMechanism { +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java new file mode 100644 index 00000000..eb4f2b68 --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java @@ -0,0 +1,8 @@ +package javasabr.mqtt.service.auth; + +import javasabr.mqtt.model.message.AuthRequest; +import reactor.core.publisher.Mono; + +public interface AuthenticationService { + Mono authenticate(AuthRequest authRequest); +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/CredentialSource.java b/authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java similarity index 81% rename from core-service/src/main/java/javasabr/mqtt/service/CredentialSource.java rename to authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java index 881361cc..9e4ca856 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/CredentialSource.java +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service; +package javasabr.mqtt.service.auth; import reactor.core.publisher.Mono; diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java new file mode 100644 index 00000000..f933a97b --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java @@ -0,0 +1,31 @@ +package javasabr.mqtt.service.auth; + +import javasabr.mqtt.model.message.AuthRequest; +import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.rlib.common.util.StringUtils; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +public class DefaultAuthenticationService implements AuthenticationService { + + RefToRefDictionary providers; + AuthenticationProvider defaultProvider; + boolean allowAnonymousAuth; + + @Override + public Mono authenticate(AuthRequest request) { + String username = request.username(); + if (allowAnonymousAuth && StringUtils.isEmpty(username)) { + return Mono.just(Boolean.TRUE); + } else { + return providers + .getOrDefault(request.authenticationMethod(), defaultProvider) + .authenticate(username, request.password(), request.authenticationData()); + } + } +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java b/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java new file mode 100644 index 00000000..f70a6243 --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.service.auth; + +import org.jspecify.annotations.NullMarked; diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java new file mode 100644 index 00000000..e39386fc --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.service.auth.provider; + +import javasabr.rlib.common.util.StringUtils; +import reactor.core.publisher.Mono; + +public class AnonymousProvider implements AuthenticationProvider { + + @Override + public String getAuthMethodName() { + return StringUtils.EMPTY; + } + + @Override + public Mono authenticate(String username, byte[] password, byte[] data) { + return Mono.just(Boolean.TRUE); + } +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java new file mode 100644 index 00000000..f0e85634 --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java @@ -0,0 +1,11 @@ +package javasabr.mqtt.service.auth.provider; + +import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Mono; + +public interface AuthenticationProvider { + + String getAuthMethodName(); + + Mono authenticate(@Nullable String username, byte[] password, byte[] data); +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java new file mode 100644 index 00000000..04e13fac --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.service.auth.provider; + +import javasabr.rlib.common.util.StringUtils; +import reactor.core.publisher.Mono; + +public class DenyProvider implements AuthenticationProvider { + + @Override + public String getAuthMethodName() { + return StringUtils.EMPTY; + } + + @Override + public Mono authenticate(String username, byte[] password, byte[] data) { + return Mono.just(false); + } +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java new file mode 100644 index 00000000..a2e1638d --- /dev/null +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.service.auth.provider; + +public enum ProviderResponse { + + SUCCESS, FAILURE, CONTINUE; +} diff --git a/basic-authentication/build.gradle b/basic-authentication/build.gradle new file mode 100644 index 00000000..22f9fd31 --- /dev/null +++ b/basic-authentication/build.gradle @@ -0,0 +1,19 @@ +plugins { + id("java-library") + id("configure-java") + id("groovy") +} + +description = "Basic authentication service" + +dependencies { + api projects.base + api projects.authentication + + implementation 'io.r2dbc:r2dbc-spi:1.0.0.RELEASE' + implementation 'io.r2dbc:r2dbc-pool:1.0.2.RELEASE' + implementation 'org.postgresql:r2dbc-postgresql:1.1.1.RELEASE' + + testImplementation projects.testSupport + testFixturesApi projects.testSupport +} diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java new file mode 100644 index 00000000..ac9ea499 --- /dev/null +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java @@ -0,0 +1,28 @@ +package javasabr.mqtt.service.auth; + +import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PasswordBasedAuthenticationProvider implements AuthenticationProvider { + + CredentialSource credentialsSource; + @Getter + String getAuthMethodName; + + @Override + public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { + // processData(data); + if (username == null) { + return Mono.just(false); + } else { + return credentialsSource.check(username, password); + } + } +} diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java new file mode 100644 index 00000000..f70a6243 --- /dev/null +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.service.auth; + +import org.jspecify.annotations.NullMarked; diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/AbstractCredentialSource.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java similarity index 90% rename from core-service/src/main/java/javasabr/mqtt/service/impl/AbstractCredentialSource.java rename to basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java index 25bd229f..e9bfb630 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/AbstractCredentialSource.java +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.service.impl; +package javasabr.mqtt.service.auth.source; import java.util.Arrays; -import javasabr.mqtt.service.CredentialSource; +import javasabr.mqtt.service.auth.CredentialSource; import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; import javasabr.rlib.collections.dictionary.RefToRefDictionary; @@ -39,6 +39,6 @@ public Mono check(String user, byte[] pass) { @Override public Mono check(byte[] pass) { - return Mono.just(Boolean.FALSE); + return Mono.just(false); } } diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java new file mode 100644 index 00000000..8ca380db --- /dev/null +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java @@ -0,0 +1,65 @@ +package javasabr.mqtt.service.auth.source; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import java.time.Duration; +import java.util.Arrays; +import javasabr.mqtt.service.auth.CredentialSource; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Mono; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DbCredentialsSource implements CredentialSource { + + private static final String DB_USER = "dbuser"; + private static final String DB_PASSWORD = "dbpassword"; + private static final String DATABASE_URL = + "r2dbc:postgresql://localhost:5432/credentials_db" + "?user=" + DB_USER + "&password=" + DB_PASSWORD; + + private final ConnectionPool connectionPool; + + private static final String SELECT_PASSWORD_SQL = "SELECT password FROM user_credentials WHERE username = $1"; + + private final String sql; + + public DbCredentialsSource(String dbUrl, String sql) { + ConnectionFactory connectionFactory = ConnectionFactories.get(dbUrl); + + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration + .builder(connectionFactory) + .maxIdleTime(Duration.ofMinutes(30)) + .maxSize(10) + .initialSize(5) + .build(); + + this.connectionPool = new ConnectionPool(configuration); + this.sql = sql; + } + + private Mono executeQuery(Connection connection, String username) { + return Mono + .from(connection + .createStatement(sql) + .bind("$1", username) + .execute()) + .flatMapMany(result -> result.map((row, _) -> row.get("password", byte[].class))) + .singleOrEmpty(); + } + + @Override + public Mono check(String user, byte[] pass) { + return Mono + .usingWhen(connectionPool.create(), connection -> executeQuery(connection, user), Connection::close) + .map(existingPass -> Arrays.equals(existingPass, pass)) + .defaultIfEmpty(false); + } + + @Override + public Mono check(byte[] pass) { + return Mono.just(false); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java similarity index 97% rename from core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java rename to basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java index 589d11b3..878d443a 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.impl; +package javasabr.mqtt.service.auth.source; import java.io.FileInputStream; import java.io.IOException; diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java new file mode 100644 index 00000000..67648f86 --- /dev/null +++ b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.service.auth.source; + +import org.jspecify.annotations.NullMarked; diff --git a/core-service/build.gradle b/core-service/build.gradle index 04b7b9e8..53ba9ee4 100644 --- a/core-service/build.gradle +++ b/core-service/build.gradle @@ -9,7 +9,8 @@ description = "Provides interfaces and minimal implementation of all required se dependencies { api projects.network api projects.aclEngine + api projects.basicAuthentication testImplementation projects.testSupport testImplementation testFixtures(projects.network) -} \ No newline at end of file +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/AuthenticationService.java b/core-service/src/main/java/javasabr/mqtt/service/AuthenticationService.java deleted file mode 100644 index 925d27dd..00000000 --- a/core-service/src/main/java/javasabr/mqtt/service/AuthenticationService.java +++ /dev/null @@ -1,7 +0,0 @@ -package javasabr.mqtt.service; - -import reactor.core.publisher.Mono; - -public interface AuthenticationService { - Mono auth(String userName, byte[] password); -} diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/SimpleAuthenticationService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/SimpleAuthenticationService.java deleted file mode 100644 index 6f18b39b..00000000 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/SimpleAuthenticationService.java +++ /dev/null @@ -1,22 +0,0 @@ -package javasabr.mqtt.service.impl; - -import javasabr.mqtt.service.AuthenticationService; -import javasabr.mqtt.service.CredentialSource; -import lombok.RequiredArgsConstructor; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -public class SimpleAuthenticationService implements AuthenticationService { - - private final CredentialSource credentialSource; - private final boolean allowAnonymousAuth; - - @Override - public Mono auth(String userName, byte[] password) { - if (allowAnonymousAuth && userName.isEmpty()) { - return Mono.just(Boolean.TRUE); - } else { - return credentialSource.check(userName, password); - } - } -} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 9c6f1dc1..280fb356 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -23,10 +23,10 @@ import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.network.session.NetworkMqttSession; import javasabr.mqtt.network.user.ConfigurableNetworkMqttUser; -import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.auth.AuthenticationService; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; @@ -74,7 +74,7 @@ protected void processValidMessage( ConnectMqttInMessage message) { resolveClientConnectionConfig(user, message); authenticationService - .auth(message.username(), message.password()) + .authenticate(message) .flatMap(ifTrue( user, message, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(user, connectAckReasonCode))) diff --git a/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java b/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java new file mode 100644 index 00000000..72387f43 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.model.message; + +public interface AuthRequest { + + String username(); + + byte[] password(); + + String authenticationMethod(); + + byte[] authenticationData(); +} diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java index d41e86bb..db7ef96d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java @@ -8,6 +8,7 @@ import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; +import javasabr.mqtt.model.message.AuthRequest; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -25,7 +26,7 @@ @Getter @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) -public class ConnectMqttInMessage extends MqttInMessage { +public class ConnectMqttInMessage extends MqttInMessage implements AuthRequest { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.CONNECT.ordinal(); diff --git a/settings.gradle b/settings.gradle index 1b6ab9f7..259ee1a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,3 +12,5 @@ include(":test-support") include(":test-coverage") include(":acl-groovy-dsl") include(":acl-engine") +include(":authentication") +include(":basic-authentication") From 3c353c45b24bf99967becac23b05a9d4913942ff Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 6 Dec 2025 14:29:55 +0100 Subject: [PATCH 002/107] [broker-67] Remove unnecessary import --- .../main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy index 639ea034..e7b4c050 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy @@ -1,6 +1,5 @@ package javasabr.mqtt.service.acl -import groovy.transform.Field import javasabr.mqtt.model.acl.Operation import javasabr.mqtt.model.acl.rule.Rule import javasabr.mqtt.model.exception.AclConfigurationException From bfef50e1eb19ecdf7875932e711f0572a26151ba Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:12:10 +0100 Subject: [PATCH 003/107] [broker-67] Implement file and db credentials sources --- application/build.gradle | 4 +- .../DatabaseCredentialsSourceConfig.java | 19 ++++ .../config/MqttBrokerSpringConfig.java | 84 ++++++++-------- .../application/AuthenticationTest.groovy | 97 +++++++++++++++++++ .../resources/application-test.properties | 12 +++ .../build.gradle | 0 .../mqtt/service/auth/CredentialSource.java | 4 +- .../PasswordBasedAuthenticationProvider.java | 10 +- .../mqtt/service/auth/package-info.java | 0 .../auth/source/AbstractCredentialSource.java | 6 +- .../service/auth/source/package-info.java | 0 .../auth/DefaultAuthenticationService.java | 2 +- .../auth/provider/AnonymousProvider.java | 2 +- .../auth/provider/ProviderResponse.java | 6 -- .../auth/source/DbCredentialsSource.java | 65 ------------- core-service/build.gradle | 2 +- credentials-source-db/build.gradle | 19 ++++ .../source/DatabaseCredentialsSource.java | 86 ++++++++++++++++ .../service/auth/source/package-info.java | 4 + credentials-source-file/build.gradle | 15 +++ .../auth/source/FileCredentialsSource.java | 5 + .../service/auth/source/package-info.java | 4 + settings.gradle | 4 +- 23 files changed, 319 insertions(+), 131 deletions(-) create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy rename {basic-authentication => authentication-basic}/build.gradle (100%) rename {authentication => authentication-basic}/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java (57%) rename {basic-authentication => authentication-basic}/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java (83%) rename {basic-authentication => authentication-basic}/src/main/java/javasabr/mqtt/service/auth/package-info.java (100%) rename {basic-authentication => authentication-basic}/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java (88%) rename {basic-authentication => authentication-basic}/src/main/java/javasabr/mqtt/service/auth/source/package-info.java (100%) delete mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java delete mode 100644 basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java create mode 100644 credentials-source-db/build.gradle create mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java create mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java create mode 100644 credentials-source-file/build.gradle rename {basic-authentication => credentials-source-file}/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java (95%) create mode 100644 credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java diff --git a/application/build.gradle b/application/build.gradle index b93e391f..148115ad 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -9,6 +9,8 @@ description = "Standard configuration of standalone version of MQTT Broker" dependencies { implementation projects.coreService + implementation projects.credentialsSourceFile + implementation projects.credentialsSourceDb implementation libs.rlib.logger.slf4j implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 @@ -24,4 +26,4 @@ tasks.withType(GroovyCompile).configureEach { configurations.each { it.exclude group: "org.slf4j", module: "slf4j-log4j12" it.exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" -} \ No newline at end of file +} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java new file mode 100644 index 00000000..773fa180 --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java @@ -0,0 +1,19 @@ +package javasabr.mqtt.broker.application.config; + +import java.time.Duration; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "credentials.source.db") +public record DatabaseCredentialsSourceConfig( + String username, + String password, + String driver, + String host, + int port, + String name, + String credentialsQuery, + Duration maxIdleTime, + int initialPoolSize, + int maxPoolSize, + String lockTimeout, + String statementTimeout) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 5ed4ea51..1246488d 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -1,11 +1,9 @@ package javasabr.mqtt.broker.application.config; import java.net.InetSocketAddress; +import java.time.Duration; import java.util.Collection; import java.util.List; -import javasabr.mqtt.service.auth.provider.AuthenticationProvider; -import javasabr.mqtt.service.auth.AuthenticationService; -import javasabr.mqtt.service.auth.DefaultAuthenticationService; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -16,13 +14,18 @@ import javasabr.mqtt.network.user.NetworkMqttUserFactory; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; -import javasabr.mqtt.service.auth.CredentialSource; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; +import javasabr.mqtt.service.auth.AuthenticationService; +import javasabr.mqtt.service.auth.CredentialSource; +import javasabr.mqtt.service.auth.DefaultAuthenticationService; import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; +import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.mqtt.service.auth.source.DatabaseCredentialsSource; +import javasabr.mqtt.service.auth.source.FileCredentialsSource; import javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; @@ -31,7 +34,6 @@ import javasabr.mqtt.service.impl.DefaultPublishReceivingService; import javasabr.mqtt.service.impl.DefaultTopicService; import javasabr.mqtt.service.impl.ExternalNetworkMqttUserFactory; -import javasabr.mqtt.service.auth.source.FileCredentialsSource; import javasabr.mqtt.service.impl.InMemoryClientIdRegistry; import javasabr.mqtt.service.impl.InMemorySubscriptionService; import javasabr.mqtt.service.message.handler.MqttInMessageHandler; @@ -64,6 +66,7 @@ import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -71,6 +74,7 @@ @CustomLog @Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(DatabaseCredentialsSourceConfig.class) public class MqttBrokerSpringConfig { @Bean @@ -94,16 +98,35 @@ CredentialSource credentialSource( return new FileCredentialsSource(fileName); } + @Bean + CredentialSource dbCredentialSource(DatabaseCredentialsSourceConfig databaseCredentialsSourceConfig) { + return DatabaseCredentialsSource + .builder() + .dbPort(databaseCredentialsSourceConfig.port()) + .dbDriver(databaseCredentialsSourceConfig.driver()) + .dbName(databaseCredentialsSourceConfig.name()) + .dbHost(databaseCredentialsSourceConfig.host()) + .dbUsername(databaseCredentialsSourceConfig.username()) + .dbPassword(databaseCredentialsSourceConfig.password()) + .maxIdleTime(databaseCredentialsSourceConfig.maxIdleTime()) + .initialPoolSize(databaseCredentialsSourceConfig.initialPoolSize()) + .maxPoolSize(databaseCredentialsSourceConfig.maxPoolSize()) + .credentialsQuery(databaseCredentialsSourceConfig.credentialsQuery()) + .lockTimeout(databaseCredentialsSourceConfig.lockTimeout()) + .statementTimeout(databaseCredentialsSourceConfig.statementTimeout()) + .build(); + } + @Bean AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { - return new PasswordBasedAuthenticationProvider(credentialSource, "basic"); + return new PasswordBasedAuthenticationProvider(credentialSource); } @Bean AuthenticationService authenticationService( List credentialSource, - @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth) { - String defaultProviderName = "basic"; + @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth, + @Value("${authentication.provider.default:basic}") String defaultProviderName) { var authenticationProviders = DictionaryFactory.mutableRefToRefDictionary( String.class, AuthenticationProvider.class); @@ -171,10 +194,7 @@ MqttInMessageHandler publishMqttInMessageHandler( PublishReceivingService publishReceivingService, MessageOutFactoryService messageOutFactoryService, TopicService topicService) { - return new PublishMqttInMessageHandler( - publishReceivingService, - messageOutFactoryService, - topicService); + return new PublishMqttInMessageHandler(publishReceivingService, messageOutFactoryService, topicService); } @Bean @@ -205,10 +225,7 @@ MqttInMessageHandler unsubscribeMqttInMessageHandler( SubscriptionService subscriptionService, MessageOutFactoryService messageOutFactoryService, TopicService topicService) { - return new UnsubscribeMqttInMessageHandler( - subscriptionService, - messageOutFactoryService, - topicService); + return new UnsubscribeMqttInMessageHandler(subscriptionService, messageOutFactoryService, topicService); } @Bean @@ -248,10 +265,7 @@ MqttPublishInMessageHandler qos0MqttPublishInMessageHandler( SubscriptionService subscriptionService, PublishDeliveringService publishDeliveringService, MessageOutFactoryService messageOutFactoryService) { - return new Qos0MqttPublishInMessageHandler( - subscriptionService, - publishDeliveringService, - messageOutFactoryService); + return new Qos0MqttPublishInMessageHandler(subscriptionService, publishDeliveringService, messageOutFactoryService); } @Bean @@ -259,10 +273,7 @@ MqttPublishInMessageHandler qos1MqttPublishInMessageHandler( SubscriptionService subscriptionService, PublishDeliveringService publishDeliveringService, MessageOutFactoryService messageOutFactoryService) { - return new Qos1MqttPublishInMessageHandler( - subscriptionService, - publishDeliveringService, - messageOutFactoryService); + return new Qos1MqttPublishInMessageHandler(subscriptionService, publishDeliveringService, messageOutFactoryService); } @Bean @@ -270,10 +281,7 @@ MqttPublishInMessageHandler qos2MqttPublishInMessageHandler( SubscriptionService subscriptionService, PublishDeliveringService publishDeliveringService, MessageOutFactoryService messageOutFactoryService) { - return new Qos2MqttPublishInMessageHandler( - subscriptionService, - publishDeliveringService, - messageOutFactoryService); + return new Qos2MqttPublishInMessageHandler(subscriptionService, publishDeliveringService, messageOutFactoryService); } @Bean @@ -298,22 +306,10 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { "mqtt.external.connection.max.message.size", int.class, MqttProperties.MAXIMUM_MESSAGE_SIZE_DEFAULT), - env.getProperty( - "mqtt.external.connection.max.string.length", - int.class, - MqttProperties.MAXIMUM_STRING_LENGTH), - env.getProperty( - "mqtt.external.connection.max.binary.size", - int.class, - MqttProperties.MAXIMUM_BINARY_SIZE), - env.getProperty( - "mqtt.external.connection.max.topic.levels", - int.class, - MqttProperties.MAXIMUM_TOPIC_LEVELS), - env.getProperty( - "mqtt.external.connection.min.keep.alive", - int.class, - MqttProperties.SERVER_KEEP_ALIVE_DEFAULT), + env.getProperty("mqtt.external.connection.max.string.length", int.class, MqttProperties.MAXIMUM_STRING_LENGTH), + env.getProperty("mqtt.external.connection.max.binary.size", int.class, MqttProperties.MAXIMUM_BINARY_SIZE), + env.getProperty("mqtt.external.connection.max.topic.levels", int.class, MqttProperties.MAXIMUM_TOPIC_LEVELS), + env.getProperty("mqtt.external.connection.min.keep.alive", int.class, MqttProperties.SERVER_KEEP_ALIVE_DEFAULT), env.getProperty( "mqtt.external.connection.receive.maximum", int.class, diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy new file mode 100644 index 00000000..0e486118 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy @@ -0,0 +1,97 @@ +package javasabr.mqtt.broker.application + +import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException +import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth +import com.hivemq.client.mqtt.mqtt3.message.connect.Mqtt3Connect +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth +import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect +import org.springframework.test.context.TestPropertySource + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletionException + +@TestPropertySource(properties = ["authentication.allow.anonymous=false"]) +class AuthenticationTest extends IntegrationSpecification { + + def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt3ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } + + def "should be able to connect with correct password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def correctPassword = "password" + and: + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } + + def "should not be able to connect with wrong password using mqtt 5 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt5ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } + + def "should be able to connect with correct password using mqtt 5 client"() { + given: + def existingUsername = "user" + def correctPassword = "password" + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } +} diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index d3241f77..c3a1e681 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -2,3 +2,15 @@ authentication.allow.anonymous=true credentials.source.file.name=credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true +credentials.source.db.username=user +credentials.source.db.password=pass +credentials.source.db.driver=postgres +credentials.source.db.host=localhost +credentials.source.db.port=5432 +credentials.source.db.name=database-name +credentials.source.db.credentials-query=SELECT password FROM user_credentials WHERE username = $1 +credentials.source.db.max-idle-time=30m +credentials.source.db.initial-pool-size=5 +credentials.source.db.max-pool-size=10 +credentials.source.db.lock-timeout=10s +credentials.source.db.statement-timeout=5m diff --git a/basic-authentication/build.gradle b/authentication-basic/build.gradle similarity index 100% rename from basic-authentication/build.gradle rename to authentication-basic/build.gradle diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java similarity index 57% rename from authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java index 9e4ca856..b31f72d0 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java @@ -4,7 +4,7 @@ public interface CredentialSource { - Mono check(String user, byte[] pass); + String getName(); - Mono check(byte[] pass); + Mono isCredentialExists(String user, byte[] pass); } diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java similarity index 83% rename from basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java index ac9ea499..cded14cf 100644 --- a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java @@ -2,7 +2,6 @@ import javasabr.mqtt.service.auth.provider.AuthenticationProvider; import lombok.AccessLevel; -import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import org.jspecify.annotations.Nullable; @@ -13,8 +12,11 @@ public class PasswordBasedAuthenticationProvider implements AuthenticationProvider { CredentialSource credentialsSource; - @Getter - String getAuthMethodName; + + @Override + public String getAuthMethodName() { + return "basic"; + } @Override public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { @@ -22,7 +24,7 @@ public Mono authenticate(@Nullable String username, byte[] password, by if (username == null) { return Mono.just(false); } else { - return credentialsSource.check(username, password); + return credentialsSource.isCredentialExists(username, password); } } } diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/package-info.java similarity index 100% rename from basic-authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/package-info.java diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java similarity index 88% rename from basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java index e9bfb630..bc808cb9 100644 --- a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java @@ -33,12 +33,8 @@ void put(String user, byte[] pass) { } @Override - public Mono check(String user, byte[] pass) { + public Mono isCredentialExists(String user, byte[] pass) { return Mono.just(Arrays.equals(pass, credentials.get(user))); } - @Override - public Mono check(byte[] pass) { - return Mono.just(false); - } } diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java similarity index 100% rename from basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/package-info.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java index f933a97b..ac2cc4fc 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java @@ -21,7 +21,7 @@ public class DefaultAuthenticationService implements AuthenticationService { public Mono authenticate(AuthRequest request) { String username = request.username(); if (allowAnonymousAuth && StringUtils.isEmpty(username)) { - return Mono.just(Boolean.TRUE); + return Mono.just(true); } else { return providers .getOrDefault(request.authenticationMethod(), defaultProvider) diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java index e39386fc..23b3f0bd 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java @@ -12,6 +12,6 @@ public String getAuthMethodName() { @Override public Mono authenticate(String username, byte[] password, byte[] data) { - return Mono.just(Boolean.TRUE); + return Mono.just(true); } } diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java deleted file mode 100644 index a2e1638d..00000000 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/ProviderResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.service.auth.provider; - -public enum ProviderResponse { - - SUCCESS, FAILURE, CONTINUE; -} diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java b/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java deleted file mode 100644 index 8ca380db..00000000 --- a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/DbCredentialsSource.java +++ /dev/null @@ -1,65 +0,0 @@ -package javasabr.mqtt.service.auth.source; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactory; -import java.time.Duration; -import java.util.Arrays; -import javasabr.mqtt.service.auth.CredentialSource; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; -import reactor.core.publisher.Mono; - -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class DbCredentialsSource implements CredentialSource { - - private static final String DB_USER = "dbuser"; - private static final String DB_PASSWORD = "dbpassword"; - private static final String DATABASE_URL = - "r2dbc:postgresql://localhost:5432/credentials_db" + "?user=" + DB_USER + "&password=" + DB_PASSWORD; - - private final ConnectionPool connectionPool; - - private static final String SELECT_PASSWORD_SQL = "SELECT password FROM user_credentials WHERE username = $1"; - - private final String sql; - - public DbCredentialsSource(String dbUrl, String sql) { - ConnectionFactory connectionFactory = ConnectionFactories.get(dbUrl); - - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration - .builder(connectionFactory) - .maxIdleTime(Duration.ofMinutes(30)) - .maxSize(10) - .initialSize(5) - .build(); - - this.connectionPool = new ConnectionPool(configuration); - this.sql = sql; - } - - private Mono executeQuery(Connection connection, String username) { - return Mono - .from(connection - .createStatement(sql) - .bind("$1", username) - .execute()) - .flatMapMany(result -> result.map((row, _) -> row.get("password", byte[].class))) - .singleOrEmpty(); - } - - @Override - public Mono check(String user, byte[] pass) { - return Mono - .usingWhen(connectionPool.create(), connection -> executeQuery(connection, user), Connection::close) - .map(existingPass -> Arrays.equals(existingPass, pass)) - .defaultIfEmpty(false); - } - - @Override - public Mono check(byte[] pass) { - return Mono.just(false); - } -} diff --git a/core-service/build.gradle b/core-service/build.gradle index 53ba9ee4..8f5f1f40 100644 --- a/core-service/build.gradle +++ b/core-service/build.gradle @@ -9,7 +9,7 @@ description = "Provides interfaces and minimal implementation of all required se dependencies { api projects.network api projects.aclEngine - api projects.basicAuthentication + api projects.authenticationBasic testImplementation projects.testSupport testImplementation testFixtures(projects.network) diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle new file mode 100644 index 00000000..1795b990 --- /dev/null +++ b/credentials-source-db/build.gradle @@ -0,0 +1,19 @@ +plugins { + id("java-library") + id("configure-java") + id("groovy") +} + +description = "Database-based Credentials Source Provider" + +dependencies { + api projects.base + api projects.authenticationBasic + + implementation 'io.r2dbc:r2dbc-spi:1.0.0.RELEASE' + implementation 'io.r2dbc:r2dbc-pool:1.0.2.RELEASE' + implementation 'org.postgresql:r2dbc-postgresql:1.1.1.RELEASE' + + testImplementation projects.testSupport + testFixturesApi projects.testSupport +} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java new file mode 100644 index 00000000..6f210ce3 --- /dev/null +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java @@ -0,0 +1,86 @@ +package javasabr.mqtt.service.auth.source; + +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactoryOptions; +import java.time.Duration; +import java.util.Arrays; +import java.util.Map; +import javasabr.mqtt.service.auth.CredentialSource; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DatabaseCredentialsSource implements CredentialSource { + + private final ConnectionPool connectionPool; + private final String credentialsQuery; + + @Override + public String getName() { + return "database"; + } + + @Override + public Mono isCredentialExists(String user, byte[] pass) { + return Mono + .usingWhen(connectionPool.create(), connection -> executeQuery(connection, user), Connection::close) + .map(existingPass -> Arrays.equals(existingPass, pass)) + .defaultIfEmpty(false); + } + + private Mono executeQuery(Connection connection, String username) { + return Mono + .from(connection.createStatement(credentialsQuery).bind("$1", username).execute()) + .flatMapMany(resultset -> resultset.map((row, _) -> row.get("password", byte[].class))) + .singleOrEmpty(); + } + + @Builder + private static CredentialSource dbCredentialSource( + String dbDriver, + String dbHost, + int dbPort, + String dbUsername, + String dbPassword, + String dbName, + String lockTimeout, + String statementTimeout, + String credentialsQuery, + Duration maxIdleTime, + int initialPoolSize, + int maxPoolSize) { + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions + .builder() + .option(DRIVER, dbDriver) + .option(HOST, dbHost) + .option(PORT, dbPort) + .option(USER, dbUsername) + .option(PASSWORD, dbPassword) + .option(DATABASE, dbName) + .option(OPTIONS, Map.of("lock_timeout", lockTimeout, "statement_timeout", statementTimeout)) + .build(); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration + .builder(ConnectionFactories.get(connectionFactoryOptions)) + .maxIdleTime(maxIdleTime) + .maxSize(maxPoolSize) + .initialSize(initialPoolSize) + .build(); + ConnectionPool connectionPool = new ConnectionPool(configuration); + return new DatabaseCredentialsSource(connectionPool, credentialsQuery); + } +} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java new file mode 100644 index 00000000..67648f86 --- /dev/null +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.service.auth.source; + +import org.jspecify.annotations.NullMarked; diff --git a/credentials-source-file/build.gradle b/credentials-source-file/build.gradle new file mode 100644 index 00000000..8be74f95 --- /dev/null +++ b/credentials-source-file/build.gradle @@ -0,0 +1,15 @@ +plugins { + id("java-library") + id("configure-java") + id("groovy") +} + +description = "File-based Credentials Source Provider" + +dependencies { + api projects.base + api projects.authenticationBasic + + testImplementation projects.testSupport + testFixturesApi projects.testSupport +} diff --git a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java similarity index 95% rename from basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java rename to credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java index 878d443a..25cbd7b1 100644 --- a/basic-authentication/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java @@ -44,4 +44,9 @@ void init() { throw new CredentialsSourceException(e); } } + + @Override + public String getName() { + return "file"; + } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java new file mode 100644 index 00000000..67648f86 --- /dev/null +++ b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.service.auth.source; + +import org.jspecify.annotations.NullMarked; diff --git a/settings.gradle b/settings.gradle index 259ee1a6..50a492e7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,4 +13,6 @@ include(":test-coverage") include(":acl-groovy-dsl") include(":acl-engine") include(":authentication") -include(":basic-authentication") +include(":authentication-basic") +include(":credentials-source-file") +include(":credentials-source-db") From 3d36a833b6e81da769d9164f4b8d2e88eb366525 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:23:24 +0100 Subject: [PATCH 004/107] [broker-67] Rename CredentialsSourceDatabaseConfig --- ...a => CredentialsSourceDatabaseConfig.java} | 2 +- .../config/MqttBrokerSpringConfig.java | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename application/src/main/java/javasabr/mqtt/broker/application/config/{DatabaseCredentialsSourceConfig.java => CredentialsSourceDatabaseConfig.java} (91%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java similarity index 91% rename from application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java index 773fa180..e659c052 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialsSourceConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java @@ -4,7 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") -public record DatabaseCredentialsSourceConfig( +public record CredentialsSourceDatabaseConfig( String username, String password, String driver, diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 7e776b23..4bef0e55 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -83,7 +83,7 @@ @CustomLog @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(DatabaseCredentialsSourceConfig.class) +@EnableConfigurationProperties(CredentialsSourceDatabaseConfig.class) public class MqttBrokerSpringConfig { @Bean @@ -108,21 +108,21 @@ CredentialSource credentialSource( } @Bean - CredentialSource dbCredentialSource(DatabaseCredentialsSourceConfig databaseCredentialsSourceConfig) { + CredentialSource dbCredentialSource(CredentialsSourceDatabaseConfig credentialsSourceDatabaseConfig) { return DatabaseCredentialsSource .builder() - .dbPort(databaseCredentialsSourceConfig.port()) - .dbDriver(databaseCredentialsSourceConfig.driver()) - .dbName(databaseCredentialsSourceConfig.name()) - .dbHost(databaseCredentialsSourceConfig.host()) - .dbUsername(databaseCredentialsSourceConfig.username()) - .dbPassword(databaseCredentialsSourceConfig.password()) - .maxIdleTime(databaseCredentialsSourceConfig.maxIdleTime()) - .initialPoolSize(databaseCredentialsSourceConfig.initialPoolSize()) - .maxPoolSize(databaseCredentialsSourceConfig.maxPoolSize()) - .credentialsQuery(databaseCredentialsSourceConfig.credentialsQuery()) - .lockTimeout(databaseCredentialsSourceConfig.lockTimeout()) - .statementTimeout(databaseCredentialsSourceConfig.statementTimeout()) + .dbPort(credentialsSourceDatabaseConfig.port()) + .dbDriver(credentialsSourceDatabaseConfig.driver()) + .dbName(credentialsSourceDatabaseConfig.name()) + .dbHost(credentialsSourceDatabaseConfig.host()) + .dbUsername(credentialsSourceDatabaseConfig.username()) + .dbPassword(credentialsSourceDatabaseConfig.password()) + .maxIdleTime(credentialsSourceDatabaseConfig.maxIdleTime()) + .initialPoolSize(credentialsSourceDatabaseConfig.initialPoolSize()) + .maxPoolSize(credentialsSourceDatabaseConfig.maxPoolSize()) + .credentialsQuery(credentialsSourceDatabaseConfig.credentialsQuery()) + .lockTimeout(credentialsSourceDatabaseConfig.lockTimeout()) + .statementTimeout(credentialsSourceDatabaseConfig.statementTimeout()) .build(); } From fd0835beb96b643e29924bb6491e43f4bf3f2970 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:07:09 +0100 Subject: [PATCH 005/107] [broker-67] Add tests --- application/build.gradle | 2 + .../config/MqttBrokerSpringConfig.java | 4 +- .../application/AuthenticationTest.groovy | 241 ++++++++++++------ .../CredentialsSourceTestConfig.groovy | 37 +++ .../resources/application-test.properties | 2 +- .../resources/{ => auth}/credentials-test | 0 .../resources/auth/user-credentials-data.sql | 2 + .../auth/user-credentials-schema.sql | 5 + .../PasswordBasedAuthenticationProvider.java | 1 + .../auth/{ => source}/CredentialSource.java | 2 +- ...rce.java => InMemoryCredentialSource.java} | 7 +- .../source/DatabaseCredentialsSource.java | 4 +- .../auth/source/FileCredentialsSource.java | 4 +- 13 files changed, 223 insertions(+), 88 deletions(-) create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy rename application/src/test/resources/{ => auth}/credentials-test (100%) create mode 100644 application/src/test/resources/auth/user-credentials-data.sql create mode 100644 application/src/test/resources/auth/user-credentials-schema.sql rename authentication-basic/src/main/java/javasabr/mqtt/service/auth/{ => source}/CredentialSource.java (78%) rename authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/{AbstractCredentialSource.java => InMemoryCredentialSource.java} (84%) diff --git a/application/build.gradle b/application/build.gradle index 148115ad..7f72885e 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -15,6 +15,8 @@ dependencies { implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 + testImplementation 'org.springframework:spring-r2dbc:6.2.9' + testImplementation 'io.r2dbc:r2dbc-h2:1.0.0.RELEASE' testImplementation projects.testSupport testImplementation testFixtures(projects.network) } diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 4bef0e55..7c1642f8 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -21,7 +21,7 @@ import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; import javasabr.mqtt.service.auth.AuthenticationService; -import javasabr.mqtt.service.auth.CredentialSource; +import javasabr.mqtt.service.auth.source.CredentialSource; import javasabr.mqtt.service.auth.DefaultAuthenticationService; import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; import javasabr.mqtt.service.auth.provider.AuthenticationProvider; @@ -142,7 +142,7 @@ AuthenticationService authenticationService( credentialSource.forEach(value -> authenticationProviders.put(value.getAuthMethodName(), value)); AuthenticationProvider defaultProvider = authenticationProviders.get(defaultProviderName); if (defaultProvider == null) { - throw new IllegalArgumentException("%s authenticator provider not found".formatted(defaultProviderName)); + throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); } return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy index 0e486118..04c3815f 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy @@ -6,92 +6,181 @@ import com.hivemq.client.mqtt.mqtt3.message.connect.Mqtt3Connect import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect +import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource import java.nio.charset.StandardCharsets import java.util.concurrent.CompletionException @TestPropertySource(properties = ["authentication.allow.anonymous=false"]) -class AuthenticationTest extends IntegrationSpecification { +class AuthenticationTest { - def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt3ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } - } + static class FileCredentialsSourceTest extends IntegrationSpecification { - def "should be able to connect with correct password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def correctPassword = "password" - and: - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() - } + def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt3ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } + + def "should be able to connect with correct password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def correctPassword = "password" + and: + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } + + def "should not be able to connect with wrong password using mqtt 5 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt5ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } - def "should not be able to connect with wrong password using mqtt 5 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt5ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } + def "should be able to connect with correct password using mqtt 5 client"() { + given: + def existingUsername = "user" + def correctPassword = "password" + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } } - def "should be able to connect with correct password using mqtt 5 client"() { - given: - def existingUsername = "user" - def correctPassword = "password" - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() + @ContextConfiguration(classes = CredentialsSourceTestConfig) + static class DatabaseCredentialsSourceTest extends IntegrationSpecification { + + def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt3ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } + + def "should be able to connect with correct password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + byte[] correctPassword = new byte[]{0x01, 0xBC, 0x2A} + and: + def subscriber = buildExternalMqtt311Client() + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } + + def "should not be able to connect with wrong password using mqtt 5 client"() { + given: + def existingUsername = "user" + def wrongPassword = "password1" + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + def e = thrown(CompletionException.class) + with(e.cause as Mqtt5ConnAckException) { + message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." + } + } + + def "should be able to connect with correct password using mqtt 5 client"() { + given: + def existingUsername = "user" + byte[] correctPassword = new byte[]{0x01, 0xBC, 0x2A} + and: + def subscriber = buildExternalMqtt5Client() + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword) + .build()) + .build() + when: + subscriber.connect(connectMessage).join() + then: + noExceptionThrown() + cleanup: + subscriber.disconnect().join() + } } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy new file mode 100644 index 00000000..9e4eb172 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.broker.application + +import io.r2dbc.spi.ConnectionFactories +import io.r2dbc.spi.ConnectionFactory +import javasabr.mqtt.broker.application.config.CredentialsSourceDatabaseConfig +import javasabr.mqtt.service.auth.source.CredentialSource +import javasabr.mqtt.service.auth.source.DatabaseCredentialsSource +import org.springframework.context.annotation.Bean +import org.springframework.core.io.ClassPathResource +import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer +import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator + +class CredentialsSourceTestConfig { + + @Bean + CredentialSource credentialSource( + ConnectionFactory connectionFactory, + CredentialsSourceDatabaseConfig credentialsSourceDatabaseConfig) { + return new DatabaseCredentialsSource(connectionFactory, credentialsSourceDatabaseConfig.credentialsQuery()) + } + + @Bean + ConnectionFactory connectionFactory() { + return ConnectionFactories.get("r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1") + } + + @Bean + ConnectionFactoryInitializer datasourceInitializer(ConnectionFactory connectionFactory) { + ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer() + initializer.setConnectionFactory(connectionFactory) + ResourceDatabasePopulator populator = new ResourceDatabasePopulator() + populator.addScript(new ClassPathResource("auth/user-credentials-schema.sql")) + populator.addScript(new ClassPathResource("auth/user-credentials-data.sql")) + initializer.setDatabasePopulator(populator) + return initializer + } +} diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index c3a1e681..780e665e 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ authentication.allow.anonymous=true -credentials.source.file.name=credentials-test +credentials.source.file.name=auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true credentials.source.db.username=user diff --git a/application/src/test/resources/credentials-test b/application/src/test/resources/auth/credentials-test similarity index 100% rename from application/src/test/resources/credentials-test rename to application/src/test/resources/auth/credentials-test diff --git a/application/src/test/resources/auth/user-credentials-data.sql b/application/src/test/resources/auth/user-credentials-data.sql new file mode 100644 index 00000000..6f678741 --- /dev/null +++ b/application/src/test/resources/auth/user-credentials-data.sql @@ -0,0 +1,2 @@ +INSERT INTO user_credentials(username, password) VALUES ('user', X'01 bc 2a'); + diff --git a/application/src/test/resources/auth/user-credentials-schema.sql b/application/src/test/resources/auth/user-credentials-schema.sql new file mode 100644 index 00000000..90da78e3 --- /dev/null +++ b/application/src/test/resources/auth/user-credentials-schema.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS user_credentials; +CREATE TABLE user_credentials ( + username VARCHAR(255), + password BINARY(3) +); diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java index cded14cf..2db978e2 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java @@ -1,6 +1,7 @@ package javasabr.mqtt.service.auth; import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.mqtt.service.auth.source.CredentialSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java similarity index 78% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java index b31f72d0..29fc8bc8 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/CredentialSource.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.auth; +package javasabr.mqtt.service.auth.source; import reactor.core.publisher.Mono; diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java similarity index 84% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java index bc808cb9..1c9d4056 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/AbstractCredentialSource.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java @@ -1,22 +1,22 @@ package javasabr.mqtt.service.auth.source; import java.util.Arrays; -import javasabr.mqtt.service.auth.CredentialSource; import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; import javasabr.rlib.collections.dictionary.RefToRefDictionary; import reactor.core.publisher.Mono; -public abstract class AbstractCredentialSource implements CredentialSource { +public abstract class InMemoryCredentialSource implements CredentialSource { private final LockableRefToRefDictionary credentials = DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); abstract void init(); - void putAll(RefToRefDictionary otherCredentials) { + void reset(RefToRefDictionary otherCredentials) { long stamp = credentials.writeLock(); try { + credentials.clear(); credentials.append(otherCredentials); } finally { credentials.writeUnlock(stamp); @@ -36,5 +36,4 @@ void put(String user, byte[] pass) { public Mono isCredentialExists(String user, byte[] pass) { return Mono.just(Arrays.equals(pass, credentials.get(user))); } - } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java index 6f210ce3..f5496863 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java @@ -12,11 +12,11 @@ import io.r2dbc.pool.ConnectionPoolConfiguration; import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.time.Duration; import java.util.Arrays; import java.util.Map; -import javasabr.mqtt.service.auth.CredentialSource; import lombok.AccessLevel; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -27,7 +27,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DatabaseCredentialsSource implements CredentialSource { - private final ConnectionPool connectionPool; + private final ConnectionFactory connectionPool; private final String credentialsQuery; @Override diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java index 25cbd7b1..1ec0cd17 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java @@ -9,7 +9,7 @@ import javasabr.rlib.collections.dictionary.DictionaryCollectors; import javasabr.rlib.collections.dictionary.RefToRefDictionary; -public class FileCredentialsSource extends AbstractCredentialSource { +public class FileCredentialsSource extends InMemoryCredentialSource { private final String fileName; @@ -39,7 +39,7 @@ void init() { entry -> entry.getKey().toString(), entry -> entry.getValue().toString().getBytes(StandardCharsets.UTF_8))); - putAll(credentials); + reset(credentials); } catch (IOException e) { throw new CredentialsSourceException(e); } From 0de823c84e6bf0332af39e288d65a13a2d607c6f Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:35:34 +0100 Subject: [PATCH 006/107] [broker-67] Refactoring --- application/build.gradle | 4 +- .../CredentialsSourceDatabaseConfig.java | 3 +- .../config/MqttBrokerSpringConfig.java | 50 +++------- .../application/AuthenticationTest.groovy | 2 +- .../CredentialsSourceTestConfig.groovy | 4 +- authentication-basic/build.gradle | 4 - .../auth/source/CredentialsSourceConfig.java | 18 ++++ credentials-source-db/build.gradle | 7 +- .../source/DatabaseCredentialsSource.java | 86 ---------------- .../auth/source/R2dbcCredentialsSource.java | 99 +++++++++++++++++++ .../auth/source/FileCredentialsSource.java | 7 +- gradle/libs.versions.toml | 11 +++ 12 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java delete mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java create mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java diff --git a/application/build.gradle b/application/build.gradle index 7f72885e..1cc52bc3 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -15,8 +15,8 @@ dependencies { implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 - testImplementation 'org.springframework:spring-r2dbc:6.2.9' - testImplementation 'io.r2dbc:r2dbc-h2:1.0.0.RELEASE' + testImplementation libs.spring.r2dbc + testImplementation libs.r2dbc.h2 testImplementation projects.testSupport testImplementation testFixtures(projects.network) } diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java index e659c052..b3332469 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java @@ -1,6 +1,7 @@ package javasabr.mqtt.broker.application.config; import java.time.Duration; +import javasabr.mqtt.service.auth.source.CredentialsSourceConfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") @@ -16,4 +17,4 @@ public record CredentialsSourceDatabaseConfig( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) {} + String statementTimeout) implements CredentialsSourceConfig {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 7c1642f8..65456816 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -21,12 +21,13 @@ import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; import javasabr.mqtt.service.auth.AuthenticationService; -import javasabr.mqtt.service.auth.source.CredentialSource; import javasabr.mqtt.service.auth.DefaultAuthenticationService; import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; import javasabr.mqtt.service.auth.provider.AuthenticationProvider; -import javasabr.mqtt.service.auth.source.DatabaseCredentialsSource; +import javasabr.mqtt.service.auth.source.CredentialSource; +import javasabr.mqtt.service.auth.source.CredentialsSourceConfig; import javasabr.mqtt.service.auth.source.FileCredentialsSource; +import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource; import javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; @@ -108,22 +109,8 @@ CredentialSource credentialSource( } @Bean - CredentialSource dbCredentialSource(CredentialsSourceDatabaseConfig credentialsSourceDatabaseConfig) { - return DatabaseCredentialsSource - .builder() - .dbPort(credentialsSourceDatabaseConfig.port()) - .dbDriver(credentialsSourceDatabaseConfig.driver()) - .dbName(credentialsSourceDatabaseConfig.name()) - .dbHost(credentialsSourceDatabaseConfig.host()) - .dbUsername(credentialsSourceDatabaseConfig.username()) - .dbPassword(credentialsSourceDatabaseConfig.password()) - .maxIdleTime(credentialsSourceDatabaseConfig.maxIdleTime()) - .initialPoolSize(credentialsSourceDatabaseConfig.initialPoolSize()) - .maxPoolSize(credentialsSourceDatabaseConfig.maxPoolSize()) - .credentialsQuery(credentialsSourceDatabaseConfig.credentialsQuery()) - .lockTimeout(credentialsSourceDatabaseConfig.lockTimeout()) - .statementTimeout(credentialsSourceDatabaseConfig.statementTimeout()) - .build(); + CredentialSource dbCredentialSource(CredentialsSourceConfig credentialsSourceDatabaseConfig) { + return R2dbcCredentialsSource.builder().config(credentialsSourceDatabaseConfig).build(); } @Bean @@ -146,7 +133,7 @@ AuthenticationService authenticationService( } return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); } - + @Bean AuthorizationService authorizationService() { return new DisabledAuthorizationService(); @@ -202,7 +189,7 @@ MqttInMessageHandler publishAckMqttInMessageHandler(MessageOutFactoryService mes MqttInMessageHandler publishCompleteMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { return new PublishCompleteMqttInMessageHandler(messageOutFactoryService); } - + @Bean PublishPayloadMqttInMessageFieldValidator publishPayloadMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { @@ -214,7 +201,7 @@ PublishQosMqttInMessageFieldValidator publishQosMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { return new PublishQosMqttInMessageFieldValidator(messageOutFactoryService); } - + @Bean PublishRetainMqttInMessageFieldValidator publishRetainMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { @@ -226,13 +213,13 @@ PublishMessageExpiryIntervalMqttInMessageFieldValidator publishMessageExpiryInte MessageOutFactoryService messageOutFactoryService) { return new PublishMessageExpiryIntervalMqttInMessageFieldValidator(messageOutFactoryService); } - + @Bean PublishResponseTopicMqttInMessageFieldValidator publishResponseTopicMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { return new PublishResponseTopicMqttInMessageFieldValidator(messageOutFactoryService); } - + @Bean PublishTopicAliasMqttInMessageFieldValidator publishTopicAliasMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { @@ -371,10 +358,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { "mqtt.external.connection.receive.maximum", int.class, MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_DEFAULT), - env.getProperty( - "mqtt.external.connection.topic.alias.maximum", - int.class, - 0), + env.getProperty("mqtt.external.connection.topic.alias.maximum", int.class, 0), env.getProperty( "mqtt.external.connection.default.session.expiration.time", long.class, @@ -387,18 +371,14 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { "mqtt.external.connection.sessions.enabled", boolean.class, MqttProperties.SESSIONS_ENABLED_DEFAULT), - env.getProperty( - "mqtt.external.connection.retain.available", - boolean.class, - false), // set false because currently it's not implemented and we should not allow for clients to use it + env.getProperty("mqtt.external.connection.retain.available", boolean.class, false), + // set false because currently it's not implemented and we should not allow for clients to use it env.getProperty( "mqtt.external.connection.wildcard.subscription.available", boolean.class, MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT), - env.getProperty( - "mqtt.external.connection.subscription.id.available", - boolean.class, - false), // set false because currently it's not implemented and we should not allow for clients to use it + env.getProperty("mqtt.external.connection.subscription.id.available", boolean.class, false), + // set false because currently it's not implemented and we should not allow for clients to use it env.getProperty( "mqtt.external.connection.shared.subscription.available", boolean.class, diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy index 04c3815f..1c679605 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy @@ -100,7 +100,7 @@ class AuthenticationTest { } @ContextConfiguration(classes = CredentialsSourceTestConfig) - static class DatabaseCredentialsSourceTest extends IntegrationSpecification { + static class R2dbcCredentialsSourceTest extends IntegrationSpecification { def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { given: diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy index 9e4eb172..5235d84b 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy @@ -4,7 +4,7 @@ import io.r2dbc.spi.ConnectionFactories import io.r2dbc.spi.ConnectionFactory import javasabr.mqtt.broker.application.config.CredentialsSourceDatabaseConfig import javasabr.mqtt.service.auth.source.CredentialSource -import javasabr.mqtt.service.auth.source.DatabaseCredentialsSource +import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource import org.springframework.context.annotation.Bean import org.springframework.core.io.ClassPathResource import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer @@ -16,7 +16,7 @@ class CredentialsSourceTestConfig { CredentialSource credentialSource( ConnectionFactory connectionFactory, CredentialsSourceDatabaseConfig credentialsSourceDatabaseConfig) { - return new DatabaseCredentialsSource(connectionFactory, credentialsSourceDatabaseConfig.credentialsQuery()) + return new R2dbcCredentialsSource(connectionFactory, credentialsSourceDatabaseConfig.credentialsQuery()) } @Bean diff --git a/authentication-basic/build.gradle b/authentication-basic/build.gradle index 22f9fd31..c92e1f2f 100644 --- a/authentication-basic/build.gradle +++ b/authentication-basic/build.gradle @@ -10,10 +10,6 @@ dependencies { api projects.base api projects.authentication - implementation 'io.r2dbc:r2dbc-spi:1.0.0.RELEASE' - implementation 'io.r2dbc:r2dbc-pool:1.0.2.RELEASE' - implementation 'org.postgresql:r2dbc-postgresql:1.1.1.RELEASE' - testImplementation projects.testSupport testFixturesApi projects.testSupport } diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java new file mode 100644 index 00000000..d8764f08 --- /dev/null +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java @@ -0,0 +1,18 @@ +package javasabr.mqtt.service.auth.source; + +import java.time.Duration; + +public interface CredentialsSourceConfig { + String username(); + String password(); + String driver(); + String host(); + int port(); + String name(); + String credentialsQuery(); + Duration maxIdleTime(); + int initialPoolSize(); + int maxPoolSize(); + String lockTimeout(); + String statementTimeout(); +} diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle index 1795b990..720628aa 100644 --- a/credentials-source-db/build.gradle +++ b/credentials-source-db/build.gradle @@ -9,10 +9,9 @@ description = "Database-based Credentials Source Provider" dependencies { api projects.base api projects.authenticationBasic - - implementation 'io.r2dbc:r2dbc-spi:1.0.0.RELEASE' - implementation 'io.r2dbc:r2dbc-pool:1.0.2.RELEASE' - implementation 'org.postgresql:r2dbc-postgresql:1.1.1.RELEASE' + implementation libs.r2dbc.spi + implementation libs.r2dbc.pool + implementation libs.r2dbc.postgresql testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java deleted file mode 100644 index f5496863..00000000 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/DatabaseCredentialsSource.java +++ /dev/null @@ -1,86 +0,0 @@ -package javasabr.mqtt.service.auth.source; - -import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; -import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; -import java.time.Duration; -import java.util.Arrays; -import java.util.Map; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class DatabaseCredentialsSource implements CredentialSource { - - private final ConnectionFactory connectionPool; - private final String credentialsQuery; - - @Override - public String getName() { - return "database"; - } - - @Override - public Mono isCredentialExists(String user, byte[] pass) { - return Mono - .usingWhen(connectionPool.create(), connection -> executeQuery(connection, user), Connection::close) - .map(existingPass -> Arrays.equals(existingPass, pass)) - .defaultIfEmpty(false); - } - - private Mono executeQuery(Connection connection, String username) { - return Mono - .from(connection.createStatement(credentialsQuery).bind("$1", username).execute()) - .flatMapMany(resultset -> resultset.map((row, _) -> row.get("password", byte[].class))) - .singleOrEmpty(); - } - - @Builder - private static CredentialSource dbCredentialSource( - String dbDriver, - String dbHost, - int dbPort, - String dbUsername, - String dbPassword, - String dbName, - String lockTimeout, - String statementTimeout, - String credentialsQuery, - Duration maxIdleTime, - int initialPoolSize, - int maxPoolSize) { - ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions - .builder() - .option(DRIVER, dbDriver) - .option(HOST, dbHost) - .option(PORT, dbPort) - .option(USER, dbUsername) - .option(PASSWORD, dbPassword) - .option(DATABASE, dbName) - .option(OPTIONS, Map.of("lock_timeout", lockTimeout, "statement_timeout", statementTimeout)) - .build(); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration - .builder(ConnectionFactories.get(connectionFactoryOptions)) - .maxIdleTime(maxIdleTime) - .maxSize(maxPoolSize) - .initialSize(initialPoolSize) - .build(); - ConnectionPool connectionPool = new ConnectionPool(configuration); - return new DatabaseCredentialsSource(connectionPool, credentialsQuery); - } -} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java new file mode 100644 index 00000000..12078eaa --- /dev/null +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java @@ -0,0 +1,99 @@ +package javasabr.mqtt.service.auth.source; + +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import io.r2dbc.spi.Result; +import io.r2dbc.spi.Row; +import io.r2dbc.spi.RowMetadata; +import java.util.Arrays; +import java.util.Map; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class R2dbcCredentialsSource implements CredentialSource { + + private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; + private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; + private static final String CREDENTIALS_SOURCE_NAME = "database"; + private static final String PASSWORD_COLUMN = "password"; + private static final String USERNAME_BIND_PARAM = "$1"; + + private final ConnectionFactory connectionPool; + private final String credentialsQuery; + + @Builder + private static CredentialSource dbCredentialSourceBuilder( + CredentialsSourceConfig config) { + Map timeoutOptions = Map.of( + LOCK_TIMEOUT_OPTION, + config.lockTimeout(), + STATEMENT_TIMEOUT_OPTION, + config.statementTimeout()); + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions + .builder() + .option(DRIVER, config.driver()) + .option(HOST, config.host()) + .option(PORT, config.port()) + .option(USER, config.username()) + .option(PASSWORD, config.password()) + .option(DATABASE, config.name()) + .option(OPTIONS, timeoutOptions) + .build(); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration + .builder(ConnectionFactories.get(connectionFactoryOptions)) + .maxIdleTime(config.maxIdleTime()) + .maxSize(config.maxPoolSize()) + .initialSize(config.initialPoolSize()) + .build(); + ConnectionPool connectionPool = new ConnectionPool(configuration); + return new R2dbcCredentialsSource(connectionPool, config.credentialsQuery()); + } + + @Override + public String getName() { + return CREDENTIALS_SOURCE_NAME; + } + + @Override + public Mono isCredentialExists(String username, byte[] requestedPassword) { + return Mono + .usingWhen(connectionPool.create(), connection -> executeQuery(connection, username), Connection::close) + .map(existingPassword -> Arrays.equals(existingPassword, requestedPassword)) + .defaultIfEmpty(false); + } + + private Mono executeQuery(Connection connection, String username) { + Publisher passwordsResultset = connection + .createStatement(credentialsQuery) + .bind(USERNAME_BIND_PARAM, username) + .execute(); + return Mono.from(passwordsResultset).flatMapMany(R2dbcCredentialsSource::streamPasswordsResultset).singleOrEmpty(); + } + + private static Publisher streamPasswordsResultset(Result resultset) { + return resultset.map(R2dbcCredentialsSource::getPassword); + } + + private static byte @Nullable [] getPassword(Row row, RowMetadata rowMetadata) { + return row.get(PASSWORD_COLUMN, byte[].class); + } +} diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java index 1ec0cd17..f40e847e 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java @@ -7,10 +7,11 @@ import java.util.Properties; import javasabr.mqtt.model.exception.CredentialsSourceException; import javasabr.rlib.collections.dictionary.DictionaryCollectors; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; public class FileCredentialsSource extends InMemoryCredentialSource { + private static final String CREDENTIALS_SOURCE_NAME = "file"; + private final String fileName; public FileCredentialsSource(String fileName) { @@ -32,7 +33,7 @@ void init() { var credentialsProperties = new Properties(); credentialsProperties.load(new FileInputStream(credentialUrl.getPath())); - RefToRefDictionary credentials = credentialsProperties + var credentials = credentialsProperties .entrySet() .stream() .collect(DictionaryCollectors.toRefToRefDictionary( @@ -47,6 +48,6 @@ void init() { @Override public String getName() { - return "file"; + return CREDENTIALS_SOURCE_NAME; } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9567a4ec..d9bf735a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,12 @@ junit-jupiter = "5.13.4" junit-platform-launcher = "1.13.4" # https://mvnrepository.com/artifact/io.projectreactor/reactor-core project-reactor = "3.7.8" +# https://mvnrepository.com/artifact/io.r2dbc/r2dbc-spi +r2dbc = "1.0.0.RELEASE" +# https://mvnrepository.com/artifact/io.r2dbc/r2dbc-pool +r2dbc-pool="1.0.2.RELEASE" +# https://mvnrepository.com/artifact/org.postgresql/r2dbc-postgresql +r2dbc-postgresql="1.1.1.RELEASE" # https://mvnrepository.com/artifact/org.spockframework/spock-core spock = "2.4-M6-groovy-4.0" # https://mvnrepository.com/artifact/org.apache.groovy/groovy-all @@ -42,12 +48,17 @@ rlib-collections = { module = "javasabr.rlib:rlib-collections", version.ref = "r springboot-starter-core = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot" } springboot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2", version.ref = "springboot" } project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" } +r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "r2dbc" } +r2dbc-spi = { module ='io.r2dbc:r2dbc-spi', version.ref = "r2dbc"} +r2dbc-pool = { module = 'io.r2dbc:r2dbc-pool', version.ref = "r2dbc-pool"} +r2dbc-postgresql= { module = 'org.postgresql:r2dbc-postgresql', version.ref = "r2dbc-postgresql"} jackson-core = { module = "tools.jackson.core:jackson-core", version.ref = "jackson" } jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" } jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" } lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } +spring-r2dbc = { module = "org.springframework:spring-r2dbc", version.ref = "spring" } spring-test = { module = "org.springframework:spring-test", version.ref = "spring" } spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } spock-spring = { module = "org.spockframework:spock-spring", version.ref = "spock" } From c7c5213c01680daef565598455f034114685446b Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:15:00 +0100 Subject: [PATCH 007/107] [broker-67] Introduce AuthenticationSpringConfig --- .../config/AuthenticationSpringConfig.java | 93 +++++++++++++++++++ ... CredentialsSourceDatabaseProperties.java} | 6 +- .../config/MqttBrokerSpringConfig.java | 44 +-------- .../CredentialsSourceTestConfig.groovy | 4 +- .../resources/auth/user-credentials-data.sql | 1 - ...rceConfig.java => DatabaseProperties.java} | 2 +- credentials-source-db/build.gradle | 6 +- .../auth/source/R2dbcCredentialsSource.java | 44 --------- 8 files changed, 105 insertions(+), 95 deletions(-) create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java rename application/src/main/java/javasabr/mqtt/broker/application/config/{CredentialsSourceDatabaseConfig.java => CredentialsSourceDatabaseProperties.java} (71%) rename authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/{CredentialsSourceConfig.java => DatabaseProperties.java} (88%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java new file mode 100644 index 00000000..c7aae525 --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java @@ -0,0 +1,93 @@ +package javasabr.mqtt.broker.application.config; + +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import java.util.List; +import java.util.Map; +import javasabr.mqtt.service.auth.AuthenticationService; +import javasabr.mqtt.service.auth.DefaultAuthenticationService; +import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; +import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.mqtt.service.auth.source.CredentialSource; +import javasabr.mqtt.service.auth.source.DatabaseProperties; +import javasabr.mqtt.service.auth.source.FileCredentialsSource; +import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class AuthenticationSpringConfig { + + private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; + private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; + + @Bean + ConnectionFactory connectionFactory(DatabaseProperties config) { + Map timeoutOptions = Map.of( + LOCK_TIMEOUT_OPTION, + config.lockTimeout(), + STATEMENT_TIMEOUT_OPTION, + config.statementTimeout()); + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions + .builder() + .option(DRIVER, config.driver()) + .option(HOST, config.host()) + .option(PORT, config.port()) + .option(USER, config.username()) + .option(PASSWORD, config.password()) + .option(DATABASE, config.name()) + .option(OPTIONS, timeoutOptions) + .build(); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration + .builder(ConnectionFactories.get(connectionFactoryOptions)) + .maxIdleTime(config.maxIdleTime()) + .maxSize(config.maxPoolSize()) + .initialSize(config.initialPoolSize()) + .build(); + return new ConnectionPool(configuration); + } + + @Bean + CredentialSource credentialSource(@Value("${credentials.source.file.name:credentials}") String fileName) { + return new FileCredentialsSource(fileName); + } + + @Bean + CredentialSource dbCredentialSource(ConnectionFactory connectionFactory, DatabaseProperties databaseProperties) { + return new R2dbcCredentialsSource(connectionFactory, databaseProperties.credentialsQuery()); + } + + @Bean + AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { + return new PasswordBasedAuthenticationProvider(credentialSource); + } + + @Bean + AuthenticationService authenticationService( + List credentialSource, + @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth, + @Value("${authentication.provider.default:basic}") String defaultProviderName) { + var authenticationProviders = DictionaryFactory.mutableRefToRefDictionary( + String.class, + AuthenticationProvider.class); + credentialSource.forEach(value -> authenticationProviders.put(value.getAuthMethodName(), value)); + AuthenticationProvider defaultProvider = authenticationProviders.get(defaultProviderName); + if (defaultProvider == null) { + throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); + } + return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); + } +} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java similarity index 71% rename from application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java index b3332469..6856579b 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java @@ -1,11 +1,11 @@ package javasabr.mqtt.broker.application.config; import java.time.Duration; -import javasabr.mqtt.service.auth.source.CredentialsSourceConfig; +import javasabr.mqtt.service.auth.source.DatabaseProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") -public record CredentialsSourceDatabaseConfig( +public record CredentialsSourceDatabaseProperties( String username, String password, String driver, @@ -17,4 +17,4 @@ public record CredentialsSourceDatabaseConfig( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) implements CredentialsSourceConfig {} + String statementTimeout) implements DatabaseProperties {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 65456816..032d3644 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -21,13 +21,6 @@ import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; import javasabr.mqtt.service.auth.AuthenticationService; -import javasabr.mqtt.service.auth.DefaultAuthenticationService; -import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; -import javasabr.mqtt.service.auth.provider.AuthenticationProvider; -import javasabr.mqtt.service.auth.source.CredentialSource; -import javasabr.mqtt.service.auth.source.CredentialsSourceConfig; -import javasabr.mqtt.service.auth.source.FileCredentialsSource; -import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource; import javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; @@ -69,7 +62,6 @@ import javasabr.mqtt.service.publish.handler.impl.Qos2MqttPublishOutMessageHandler; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.mqtt.service.session.impl.InMemoryMqttSessionService; -import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.network.NetworkFactory; import javasabr.rlib.network.ServerNetworkConfig; import javasabr.rlib.network.server.ServerNetwork; @@ -80,11 +72,13 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; @CustomLog @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(CredentialsSourceDatabaseConfig.class) +@Import(AuthenticationSpringConfig.class) +@EnableConfigurationProperties(CredentialsSourceDatabaseProperties.class) public class MqttBrokerSpringConfig { @Bean @@ -102,38 +96,6 @@ MqttSessionService mqttSessionService( return new InMemoryMqttSessionService(cleanInterval); } - @Bean - CredentialSource credentialSource( - @Value("${credentials.source.file.name:credentials}") String fileName) { - return new FileCredentialsSource(fileName); - } - - @Bean - CredentialSource dbCredentialSource(CredentialsSourceConfig credentialsSourceDatabaseConfig) { - return R2dbcCredentialsSource.builder().config(credentialsSourceDatabaseConfig).build(); - } - - @Bean - AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { - return new PasswordBasedAuthenticationProvider(credentialSource); - } - - @Bean - AuthenticationService authenticationService( - List credentialSource, - @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth, - @Value("${authentication.provider.default:basic}") String defaultProviderName) { - var authenticationProviders = DictionaryFactory.mutableRefToRefDictionary( - String.class, - AuthenticationProvider.class); - credentialSource.forEach(value -> authenticationProviders.put(value.getAuthMethodName(), value)); - AuthenticationProvider defaultProvider = authenticationProviders.get(defaultProviderName); - if (defaultProvider == null) { - throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); - } - return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); - } - @Bean AuthorizationService authorizationService() { return new DisabledAuthorizationService(); diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy index 5235d84b..28ccd6e3 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy @@ -2,7 +2,7 @@ package javasabr.mqtt.broker.application import io.r2dbc.spi.ConnectionFactories import io.r2dbc.spi.ConnectionFactory -import javasabr.mqtt.broker.application.config.CredentialsSourceDatabaseConfig +import javasabr.mqtt.broker.application.config.CredentialsSourceDatabaseProperties import javasabr.mqtt.service.auth.source.CredentialSource import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource import org.springframework.context.annotation.Bean @@ -15,7 +15,7 @@ class CredentialsSourceTestConfig { @Bean CredentialSource credentialSource( ConnectionFactory connectionFactory, - CredentialsSourceDatabaseConfig credentialsSourceDatabaseConfig) { + CredentialsSourceDatabaseProperties credentialsSourceDatabaseConfig) { return new R2dbcCredentialsSource(connectionFactory, credentialsSourceDatabaseConfig.credentialsQuery()) } diff --git a/application/src/test/resources/auth/user-credentials-data.sql b/application/src/test/resources/auth/user-credentials-data.sql index 6f678741..997d61f3 100644 --- a/application/src/test/resources/auth/user-credentials-data.sql +++ b/application/src/test/resources/auth/user-credentials-data.sql @@ -1,2 +1 @@ INSERT INTO user_credentials(username, password) VALUES ('user', X'01 bc 2a'); - diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java similarity index 88% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java rename to authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java index d8764f08..db1a6bb4 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialsSourceConfig.java +++ b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java @@ -2,7 +2,7 @@ import java.time.Duration; -public interface CredentialsSourceConfig { +public interface DatabaseProperties { String username(); String password(); String driver(); diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle index 720628aa..eddc9586 100644 --- a/credentials-source-db/build.gradle +++ b/credentials-source-db/build.gradle @@ -9,9 +9,9 @@ description = "Database-based Credentials Source Provider" dependencies { api projects.base api projects.authenticationBasic - implementation libs.r2dbc.spi - implementation libs.r2dbc.pool - implementation libs.r2dbc.postgresql + api libs.r2dbc.spi + api libs.r2dbc.pool + api libs.r2dbc.postgresql testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java index 12078eaa..2d97db46 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java @@ -1,26 +1,12 @@ package javasabr.mqtt.service.auth.source; -import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; -import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.Result; import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; import java.util.Arrays; -import java.util.Map; import lombok.AccessLevel; -import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import org.jspecify.annotations.Nullable; @@ -31,8 +17,6 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class R2dbcCredentialsSource implements CredentialSource { - private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; - private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; private static final String CREDENTIALS_SOURCE_NAME = "database"; private static final String PASSWORD_COLUMN = "password"; private static final String USERNAME_BIND_PARAM = "$1"; @@ -40,34 +24,6 @@ public class R2dbcCredentialsSource implements CredentialSource { private final ConnectionFactory connectionPool; private final String credentialsQuery; - @Builder - private static CredentialSource dbCredentialSourceBuilder( - CredentialsSourceConfig config) { - Map timeoutOptions = Map.of( - LOCK_TIMEOUT_OPTION, - config.lockTimeout(), - STATEMENT_TIMEOUT_OPTION, - config.statementTimeout()); - ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions - .builder() - .option(DRIVER, config.driver()) - .option(HOST, config.host()) - .option(PORT, config.port()) - .option(USER, config.username()) - .option(PASSWORD, config.password()) - .option(DATABASE, config.name()) - .option(OPTIONS, timeoutOptions) - .build(); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration - .builder(ConnectionFactories.get(connectionFactoryOptions)) - .maxIdleTime(config.maxIdleTime()) - .maxSize(config.maxPoolSize()) - .initialSize(config.initialPoolSize()) - .build(); - ConnectionPool connectionPool = new ConnectionPool(configuration); - return new R2dbcCredentialsSource(connectionPool, config.credentialsQuery()); - } - @Override public String getName() { return CREDENTIALS_SOURCE_NAME; From 388be5173777c8cf7090790f585b176e645cc484 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 10 Dec 2025 23:34:28 +0100 Subject: [PATCH 008/107] [broker-67] Revert unnecessary changes --- .../mqtt/service/auth/AuthMechanism.java | 4 ---- .../auth/provider/AnonymousProvider.java | 17 ----------------- .../service/auth/provider/DenyProvider.java | 17 ----------------- .../auth/source/R2dbcCredentialsSource.java | 3 --- .../auth/source/FileCredentialsSource.java | 5 ++++- 5 files changed, 4 insertions(+), 42 deletions(-) delete mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java delete mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java delete mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java deleted file mode 100644 index 17f29336..00000000 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthMechanism.java +++ /dev/null @@ -1,4 +0,0 @@ -package javasabr.mqtt.service.auth; - -public enum AuthMechanism { -} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java deleted file mode 100644 index 23b3f0bd..00000000 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AnonymousProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.service.auth.provider; - -import javasabr.rlib.common.util.StringUtils; -import reactor.core.publisher.Mono; - -public class AnonymousProvider implements AuthenticationProvider { - - @Override - public String getAuthMethodName() { - return StringUtils.EMPTY; - } - - @Override - public Mono authenticate(String username, byte[] password, byte[] data) { - return Mono.just(true); - } -} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java b/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java deleted file mode 100644 index 04e13fac..00000000 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/DenyProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.service.auth.provider; - -import javasabr.rlib.common.util.StringUtils; -import reactor.core.publisher.Mono; - -public class DenyProvider implements AuthenticationProvider { - - @Override - public String getAuthMethodName() { - return StringUtils.EMPTY; - } - - @Override - public Mono authenticate(String username, byte[] password, byte[] data) { - return Mono.just(false); - } -} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java index 2d97db46..0cdd5c0e 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java @@ -6,15 +6,12 @@ import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; import java.util.Arrays; -import lombok.AccessLevel; import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class R2dbcCredentialsSource implements CredentialSource { private static final String CREDENTIALS_SOURCE_NAME = "database"; diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java index f40e847e..5d314814 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java @@ -7,12 +7,15 @@ import java.util.Properties; import javasabr.mqtt.model.exception.CredentialsSourceException; import javasabr.rlib.collections.dictionary.DictionaryCollectors; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class FileCredentialsSource extends InMemoryCredentialSource { private static final String CREDENTIALS_SOURCE_NAME = "file"; - private final String fileName; + String fileName; public FileCredentialsSource(String fileName) { this.fileName = fileName; From ed00b01fee739a49ac1c34aebf1ef64f9f4798ae Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:22:17 +0100 Subject: [PATCH 009/107] [broker-67] Introduce DatabaseClient from spring-r2dbc --- application/build.gradle | 1 - .../config/AuthenticationSpringConfig.java | 8 ++++- .../CredentialsSourceTestConfig.groovy | 7 ++-- credentials-source-db/build.gradle | 1 + .../auth/source/R2dbcCredentialsSource.java | 34 +++++-------------- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index 1cc52bc3..9f688761 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -15,7 +15,6 @@ dependencies { implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 - testImplementation libs.spring.r2dbc testImplementation libs.r2dbc.h2 testImplementation projects.testSupport testImplementation testFixtures(projects.network) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java index c7aae525..0781c024 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) public class AuthenticationSpringConfig { @@ -60,13 +61,18 @@ ConnectionFactory connectionFactory(DatabaseProperties config) { return new ConnectionPool(configuration); } + @Bean + DatabaseClient databaseClient(ConnectionFactory connectionFactory) { + return DatabaseClient.create(connectionFactory); + } + @Bean CredentialSource credentialSource(@Value("${credentials.source.file.name:credentials}") String fileName) { return new FileCredentialsSource(fileName); } @Bean - CredentialSource dbCredentialSource(ConnectionFactory connectionFactory, DatabaseProperties databaseProperties) { + CredentialSource dbCredentialSource(DatabaseClient connectionFactory, DatabaseProperties databaseProperties) { return new R2dbcCredentialsSource(connectionFactory, databaseProperties.credentialsQuery()); } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy index 28ccd6e3..8a6be36a 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy @@ -9,14 +9,13 @@ import org.springframework.context.annotation.Bean import org.springframework.core.io.ClassPathResource import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator +import org.springframework.r2dbc.core.DatabaseClient class CredentialsSourceTestConfig { @Bean - CredentialSource credentialSource( - ConnectionFactory connectionFactory, - CredentialsSourceDatabaseProperties credentialsSourceDatabaseConfig) { - return new R2dbcCredentialsSource(connectionFactory, credentialsSourceDatabaseConfig.credentialsQuery()) + CredentialSource credentialSource(DatabaseClient databaseClient, CredentialsSourceDatabaseProperties properties) { + return new R2dbcCredentialsSource(databaseClient, properties.credentialsQuery()) } @Bean diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle index eddc9586..ca3c76fa 100644 --- a/credentials-source-db/build.gradle +++ b/credentials-source-db/build.gradle @@ -12,6 +12,7 @@ dependencies { api libs.r2dbc.spi api libs.r2dbc.pool api libs.r2dbc.postgresql + api libs.spring.r2dbc testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java index 0cdd5c0e..ee09847f 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java @@ -1,14 +1,8 @@ package javasabr.mqtt.service.auth.source; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.Result; -import io.r2dbc.spi.Row; -import io.r2dbc.spi.RowMetadata; import java.util.Arrays; import lombok.RequiredArgsConstructor; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; +import org.springframework.r2dbc.core.DatabaseClient; import reactor.core.publisher.Mono; @RequiredArgsConstructor @@ -18,7 +12,7 @@ public class R2dbcCredentialsSource implements CredentialSource { private static final String PASSWORD_COLUMN = "password"; private static final String USERNAME_BIND_PARAM = "$1"; - private final ConnectionFactory connectionPool; + private final DatabaseClient databaseClient; private final String credentialsQuery; @Override @@ -28,25 +22,13 @@ public String getName() { @Override public Mono isCredentialExists(String username, byte[] requestedPassword) { - return Mono - .usingWhen(connectionPool.create(), connection -> executeQuery(connection, username), Connection::close) + return databaseClient + .sql(credentialsQuery) + .bind(USERNAME_BIND_PARAM, username) + .map(row -> row.get(PASSWORD_COLUMN, byte[].class)) + .all() + .singleOrEmpty() .map(existingPassword -> Arrays.equals(existingPassword, requestedPassword)) .defaultIfEmpty(false); } - - private Mono executeQuery(Connection connection, String username) { - Publisher passwordsResultset = connection - .createStatement(credentialsQuery) - .bind(USERNAME_BIND_PARAM, username) - .execute(); - return Mono.from(passwordsResultset).flatMapMany(R2dbcCredentialsSource::streamPasswordsResultset).singleOrEmpty(); - } - - private static Publisher streamPasswordsResultset(Result resultset) { - return resultset.map(R2dbcCredentialsSource::getPassword); - } - - private static byte @Nullable [] getPassword(Row row, RowMetadata rowMetadata) { - return row.get(PASSWORD_COLUMN, byte[].class); - } } From ef1f19b1739cc4834d475c21b22789b2ca0cc18b Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:03:16 +0100 Subject: [PATCH 010/107] [broker-67] Refactoring --- .../config/AuthenticationSpringConfig.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java index 0781c024..caa8bcab 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java @@ -38,10 +38,8 @@ public class AuthenticationSpringConfig { @Bean ConnectionFactory connectionFactory(DatabaseProperties config) { Map timeoutOptions = Map.of( - LOCK_TIMEOUT_OPTION, - config.lockTimeout(), - STATEMENT_TIMEOUT_OPTION, - config.statementTimeout()); + LOCK_TIMEOUT_OPTION, config.lockTimeout(), + STATEMENT_TIMEOUT_OPTION, config.statementTimeout()); ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions .builder() .option(DRIVER, config.driver()) @@ -52,8 +50,9 @@ ConnectionFactory connectionFactory(DatabaseProperties config) { .option(DATABASE, config.name()) .option(OPTIONS, timeoutOptions) .build(); + ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration - .builder(ConnectionFactories.get(connectionFactoryOptions)) + .builder(connectionFactory) .maxIdleTime(config.maxIdleTime()) .maxSize(config.maxPoolSize()) .initialSize(config.initialPoolSize()) @@ -83,17 +82,15 @@ AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource cred @Bean AuthenticationService authenticationService( - List credentialSource, + List authenticationProviders, @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth, @Value("${authentication.provider.default:basic}") String defaultProviderName) { - var authenticationProviders = DictionaryFactory.mutableRefToRefDictionary( - String.class, - AuthenticationProvider.class); - credentialSource.forEach(value -> authenticationProviders.put(value.getAuthMethodName(), value)); - AuthenticationProvider defaultProvider = authenticationProviders.get(defaultProviderName); + var providers = DictionaryFactory.mutableRefToRefDictionary(String.class, AuthenticationProvider.class); + authenticationProviders.forEach(value -> providers.put(value.getAuthMethodName(), value)); + AuthenticationProvider defaultProvider = providers.get(defaultProviderName); if (defaultProvider == null) { throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); } - return new DefaultAuthenticationService(authenticationProviders.toReadOnly(), defaultProvider, allowAnonymousAuth); + return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider, allowAnonymousAuth); } } From 92396360a915f83994a8b52f38047e52e94c714d Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 13 Dec 2025 16:03:34 +0100 Subject: [PATCH 011/107] [broker-67] Make AuthRequest a record --- .../mqtt/service/auth/AuthenticationService.java | 2 +- .../auth/DefaultAuthenticationService.java | 2 +- .../converter/ConnectToAuthRequestConverter.java | 16 ++++++++++++++++ .../message/converter/MessageConverter.java | 6 ++++++ .../impl/ConnectInMqttInMessageHandler.java | 7 ++++++- .../javasabr/mqtt/model/auth/AuthRequest.java | 3 +++ .../javasabr/mqtt/model/message/AuthRequest.java | 12 ------------ .../network/message/in/ConnectMqttInMessage.java | 3 +-- 8 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java create mode 100644 model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java delete mode 100644 model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java index eb4f2b68..8aa0a161 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.service.auth; -import javasabr.mqtt.model.message.AuthRequest; +import javasabr.mqtt.model.auth.AuthRequest; import reactor.core.publisher.Mono; public interface AuthenticationService { diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java index ac2cc4fc..2a79d0a1 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java +++ b/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.service.auth; -import javasabr.mqtt.model.message.AuthRequest; +import javasabr.mqtt.model.auth.AuthRequest; import javasabr.mqtt.service.auth.provider.AuthenticationProvider; import javasabr.rlib.collections.dictionary.RefToRefDictionary; import javasabr.rlib.common.util.StringUtils; diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java b/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java new file mode 100644 index 00000000..7ee8408e --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.service.message.converter; + +import javasabr.mqtt.model.auth.AuthRequest; +import javasabr.mqtt.network.message.in.ConnectMqttInMessage; + +public class ConnectToAuthRequestConverter implements MessageConverter { + + @Override + public AuthRequest convert(ConnectMqttInMessage message) { + return new AuthRequest( + message.username(), + message.password(), + message.authenticationMethod(), + message.authenticationData()); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java b/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java new file mode 100644 index 00000000..d1e14cee --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.service.message.converter; + +public interface MessageConverter { + + Out convert(In message); +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 7ab47bee..968b874b 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -15,6 +15,7 @@ import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; +import javasabr.mqtt.model.auth.AuthRequest; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -27,6 +28,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.auth.AuthenticationService; +import javasabr.mqtt.service.message.converter.ConnectToAuthRequestConverter; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; @@ -43,6 +45,7 @@ public class ConnectInMqttInMessageHandler AuthenticationService authenticationService; MqttSessionService sessionService; SubscriptionService subscriptionService; + ConnectToAuthRequestConverter connectToAuthRequestConverter; public ConnectInMqttInMessageHandler( ClientIdRegistry clientIdRegistry, @@ -55,6 +58,7 @@ public ConnectInMqttInMessageHandler( this.authenticationService = authenticationService; this.sessionService = sessionService; this.subscriptionService = subscriptionService; + this.connectToAuthRequestConverter = new ConnectToAuthRequestConverter(); } @Override @@ -73,8 +77,9 @@ protected void processValidMessage( ExternalNetworkMqttUser user, ConnectMqttInMessage message) { resolveClientConnectionConfig(user, message); + AuthRequest authRequest = connectToAuthRequestConverter.convert(message); authenticationService - .authenticate(message) + .authenticate(authRequest) .flatMap(ifTrue( user, message, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(user, connectAckReasonCode))) diff --git a/model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java b/model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java new file mode 100644 index 00000000..989b53a1 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java @@ -0,0 +1,3 @@ +package javasabr.mqtt.model.auth; + +public record AuthRequest(String username, byte[] password, String authenticationMethod, byte[] authenticationData) {} diff --git a/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java b/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java deleted file mode 100644 index 72387f43..00000000 --- a/model/src/main/java/javasabr/mqtt/model/message/AuthRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package javasabr.mqtt.model.message; - -public interface AuthRequest { - - String username(); - - byte[] password(); - - String authenticationMethod(); - - byte[] authenticationData(); -} diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java index db7ef96d..d41e86bb 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java @@ -8,7 +8,6 @@ import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; -import javasabr.mqtt.model.message.AuthRequest; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -26,7 +25,7 @@ @Getter @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) -public class ConnectMqttInMessage extends MqttInMessage implements AuthRequest { +public class ConnectMqttInMessage extends MqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.CONNECT.ordinal(); From b75fd79126b7d80343b3254e3faf9d3f60ddccc3 Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 12:53:09 +0100 Subject: [PATCH 012/107] reorganize ACL modules --- acl-engine/build.gradle | 3 -- .../engine/AclEngine.java} | 17 +++++++--- .../exception/AclConfigurationException.java | 2 +- .../mqtt/acl/engine/model/Action.java | 5 +++ .../model}/condition/AllOfCondition.java | 2 +- .../model}/condition/AnyOfCondition.java | 2 +- .../model}/condition/AnyUserCondition.java | 2 +- .../model}/condition/ClientIdCondition.java | 4 +-- .../engine/model}/condition/Condition.java | 2 +- .../model}/condition/IpAddressCondition.java | 4 +-- .../model}/condition/MqttUserCondition.java | 2 +- .../model}/condition/TopicCondition.java | 6 ++-- .../model}/condition/UserNameCondition.java | 4 +-- .../engine/model}/condition/package-info.java | 2 +- .../model}/matcher/AnyTopicMatcher.java | 2 +- .../model}/matcher/AnyValueMatcher.java | 2 +- .../engine/model}/matcher/EqualsMatcher.java | 2 +- .../engine/model}/matcher/RegexMatcher.java | 2 +- .../model}/matcher/TopicFilterMatcher.java | 2 +- .../model}/matcher/TopicNameMatcher.java | 2 +- .../engine/model}/matcher/ValueMatcher.java | 2 +- .../engine/model/matcher}/package-info.java | 2 +- .../acl/engine/model}/rule/AbstractRule.java | 6 ++-- .../engine/model}/rule/AllowPublishRule.java | 10 +++--- .../model}/rule/AllowSubscribeRule.java | 10 +++--- .../engine/model}/rule/DenyPublishRule.java | 10 +++--- .../engine/model}/rule/DenySubscribeRule.java | 10 +++--- .../mqtt/acl/engine/model}/rule/Rule.java | 4 +-- .../acl/engine/model/rule}/package-info.java | 2 +- .../mqtt/acl/engine}/package-info.java | 2 +- .../engine/AclEngineTest.groovy} | 27 ++++++++------- .../model}/condition/ConditionTest.groovy | 14 +++++--- .../engine/model}/matcher/MatcherTest.groovy | 2 +- .../acl/engine/model}/rule/RuleTest.groovy | 8 ++--- acl-groovy-dsl/build.gradle | 4 +-- .../mqtt/service/acl/AclRulesLoader.groovy | 5 ++- .../acl/builder/AclRulesBuilder.groovy | 4 +-- .../service/acl/builder/AllOfBuilder.groovy | 8 ++--- .../builder/AllowPublishRuleBuilder.groovy | 8 ++--- .../builder/AllowSubscribeRuleBuilder.groovy | 8 ++--- .../service/acl/builder/AnyOfBuilder.groovy | 4 +-- .../acl/builder/ConditionBuilder.groovy | 10 +++--- .../acl/builder/DenyPublishRuleBuilder.groovy | 8 ++--- .../builder/DenySubscribeRuleBuilder.groovy | 8 ++--- .../acl/builder/PublishRuleBuilder.groovy | 4 +-- .../service/acl/builder/RuleBuilder.groovy | 10 +++--- .../acl/builder/SubscribeRuleBuilder.groovy | 4 +-- .../java/javasabr/mqtt/model/acl/Action.java | 5 --- .../mqtt/model/acl/rule/package-info.java | 4 --- .../acl/builder/ClientMatcherBuilder.java | 6 ++-- .../acl/builder/RuleContainerBuilder.java | 2 +- .../acl/builder/TopicMatcherBuilder.java | 6 ++-- .../service/acl/AclRulesLoaderTest.groovy | 34 ++++++++++--------- .../service/acl/ConditionMatcherAware.groovy | 16 ++++----- gradle/libs.versions.toml | 2 +- 55 files changed, 173 insertions(+), 165 deletions(-) rename acl-engine/src/main/java/javasabr/mqtt/{service/acl/AclRulesEngine.java => acl/engine/AclEngine.java} (55%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model => acl-engine/src/main/java/javasabr/mqtt/acl/engine}/exception/AclConfigurationException.java (76%) create mode 100644 acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/Action.java rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/AllOfCondition.java (91%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/AnyOfCondition.java (91%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/AnyUserCondition.java (78%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/ClientIdCondition.java (70%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/Condition.java (69%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/IpAddressCondition.java (71%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/MqttUserCondition.java (77%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/TopicCondition.java (75%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/UserNameCondition.java (70%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/condition/package-info.java (53%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/AnyTopicMatcher.java (81%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/AnyValueMatcher.java (74%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/EqualsMatcher.java (81%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/RegexMatcher.java (81%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/TopicFilterMatcher.java (96%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/TopicNameMatcher.java (88%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/matcher/ValueMatcher.java (71%) rename acl-engine/src/main/java/javasabr/mqtt/{service/acl => acl/engine/model/matcher}/package-info.java (54%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/AbstractRule.java (81%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/AllowPublishRule.java (64%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/AllowSubscribeRule.java (64%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/DenyPublishRule.java (64%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/DenySubscribeRule.java (64%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model}/rule/Rule.java (75%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher => acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule}/package-info.java (55%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine}/package-info.java (62%) rename acl-engine/src/test/groovy/javasabr/mqtt/{service/acl/AclRulesEngineTest.groovy => acl/engine/AclEngineTest.groovy} (90%) rename {acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl => acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model}/condition/ConditionTest.groovy (81%) rename {acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl => acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model}/matcher/MatcherTest.groovy (98%) rename {acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl => acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model}/rule/RuleTest.groovy (90%) delete mode 100644 acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/Action.java delete mode 100644 acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/package-info.java diff --git a/acl-engine/build.gradle b/acl-engine/build.gradle index 2d2a254e..5c90514a 100644 --- a/acl-engine/build.gradle +++ b/acl-engine/build.gradle @@ -5,10 +5,7 @@ plugins { } dependencies { - api projects.aclGroovyDsl api projects.model - api libs.groovy testImplementation projects.testSupport - testImplementation testFixtures(projects.aclGroovyDsl) } diff --git a/acl-engine/src/main/java/javasabr/mqtt/service/acl/AclRulesEngine.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java similarity index 55% rename from acl-engine/src/main/java/javasabr/mqtt/service/acl/AclRulesEngine.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java index 9da0edcb..67cd6884 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/service/acl/AclRulesEngine.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java @@ -1,15 +1,22 @@ -package javasabr.mqtt.service.acl; +package javasabr.mqtt.acl.engine; import java.util.Map; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.rule.Rule; +import javasabr.mqtt.acl.engine.model.rule.Rule; import javasabr.mqtt.model.topic.AbstractTopic; import javasabr.rlib.collections.array.Array; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; -public record AclRulesEngine(Map> ruleMap) { - +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public final class AclEngine { + + Map> ruleMap; + public boolean authorize(MqttUser mqttUser, Operation operation, AbstractTopic topic) { Array rules = ruleMap.get(operation); for (Rule rule : rules) { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/exception/AclConfigurationException.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java similarity index 76% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/exception/AclConfigurationException.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java index f8d4ebd0..0c3d6f36 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/exception/AclConfigurationException.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.exception; +package javasabr.mqtt.acl.engine.exception; public class AclConfigurationException extends RuntimeException { diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/Action.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/Action.java new file mode 100644 index 00000000..a0a69976 --- /dev/null +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/Action.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.acl.engine.model; + +public enum Action { + ALLOW, DENY +} diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AllOfCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AllOfCondition.java similarity index 91% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AllOfCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AllOfCondition.java index 5cce986e..eedf26b9 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AllOfCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AllOfCondition.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; import javasabr.rlib.collections.array.Array; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyOfCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyOfCondition.java similarity index 91% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyOfCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyOfCondition.java index 2286765d..5fc79f7d 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyOfCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyOfCondition.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; import javasabr.rlib.collections.array.Array; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyUserCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyUserCondition.java similarity index 78% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyUserCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyUserCondition.java index 57258bcf..040ab5cc 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/AnyUserCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/AnyUserCondition.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/ClientIdCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java similarity index 70% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/ClientIdCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java index 7833eac3..01808632 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/ClientIdCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; public record ClientIdCondition(ValueMatcher expectedClientId) implements MqttUserCondition { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/Condition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/Condition.java similarity index 69% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/Condition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/Condition.java index c9c9e811..dcbdb3f1 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/Condition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/Condition.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import org.jspecify.annotations.Nullable; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/IpAddressCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java similarity index 71% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/IpAddressCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java index abca7f62..07642291 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/IpAddressCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; public record IpAddressCondition(ValueMatcher expectedIpAddress) implements MqttUserCondition { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/MqttUserCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/MqttUserCondition.java similarity index 77% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/MqttUserCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/MqttUserCondition.java index 1caf75b0..2dfbc892 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/MqttUserCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/MqttUserCondition.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/TopicCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/TopicCondition.java similarity index 75% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/TopicCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/TopicCondition.java index a73d90c1..d42c716d 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/TopicCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/TopicCondition.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; -import javasabr.mqtt.model.acl.matcher.AnyTopicMatcher; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.AnyTopicMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; import javasabr.mqtt.model.topic.AbstractTopic; import javasabr.rlib.collections.array.Array; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/UserNameCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java similarity index 70% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/UserNameCondition.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java index 7e859916..fc08667b 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/UserNameCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; public record UserNameCondition(ValueMatcher userNameMatcher) implements MqttUserCondition { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/package-info.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/package-info.java similarity index 53% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/package-info.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/package-info.java index 85bbc1a0..46d81344 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/condition/package-info.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.model.acl.condition; +package javasabr.mqtt.acl.engine.model.condition; import org.jspecify.annotations.NullMarked; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyTopicMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyTopicMatcher.java similarity index 81% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyTopicMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyTopicMatcher.java index 4011bb39..a5feb871 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyTopicMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyTopicMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; import javasabr.mqtt.model.topic.AbstractTopic; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyValueMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyValueMatcher.java similarity index 74% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyValueMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyValueMatcher.java index a1baec73..f8043119 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/AnyValueMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/AnyValueMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; public record AnyValueMatcher() implements ValueMatcher { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/EqualsMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/EqualsMatcher.java similarity index 81% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/EqualsMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/EqualsMatcher.java index c2d2d5df..9f637aaf 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/EqualsMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/EqualsMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; import java.util.Objects; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/RegexMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/RegexMatcher.java similarity index 81% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/RegexMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/RegexMatcher.java index 18d465c8..ac185959 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/RegexMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/RegexMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; import java.util.regex.Pattern; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicFilterMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicFilterMatcher.java similarity index 96% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicFilterMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicFilterMatcher.java index 824eb8e4..9f8d52cb 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicFilterMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicFilterMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; import java.util.Objects; import javasabr.mqtt.model.topic.AbstractTopic; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicNameMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicNameMatcher.java similarity index 88% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicNameMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicNameMatcher.java index 48a3b494..87f131fe 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/TopicNameMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/TopicNameMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; import java.util.Objects; import javasabr.mqtt.model.topic.AbstractTopic; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/ValueMatcher.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/ValueMatcher.java similarity index 71% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/ValueMatcher.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/ValueMatcher.java index 0cd9443c..e6ee701a 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/ValueMatcher.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/ValueMatcher.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.matcher; public interface ValueMatcher { diff --git a/acl-engine/src/main/java/javasabr/mqtt/service/acl/package-info.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/package-info.java similarity index 54% rename from acl-engine/src/main/java/javasabr/mqtt/service/acl/package-info.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/package-info.java index 58140c55..b8d514c8 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/service/acl/package-info.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/matcher/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.service.acl; +package javasabr.mqtt.acl.engine.model.matcher; import org.jspecify.annotations.NullMarked; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AbstractRule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AbstractRule.java similarity index 81% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AbstractRule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AbstractRule.java index b0574553..ac0b10cb 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AbstractRule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AbstractRule.java @@ -1,9 +1,9 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.condition.MqttUserCondition; -import javasabr.mqtt.model.acl.condition.TopicCondition; import javasabr.mqtt.model.topic.AbstractTopic; import lombok.AccessLevel; import lombok.EqualsAndHashCode; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowPublishRule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowPublishRule.java similarity index 64% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowPublishRule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowPublishRule.java index 6b7cbc2a..0d7713b5 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowPublishRule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowPublishRule.java @@ -1,12 +1,12 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; -import static javasabr.mqtt.model.acl.Action.ALLOW; +import static javasabr.mqtt.acl.engine.model.Action.ALLOW; import static javasabr.mqtt.model.acl.Operation.PUBLISH; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.condition.MqttUserCondition; -import javasabr.mqtt.model.acl.condition.TopicCondition; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowSubscribeRule.java similarity index 64% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowSubscribeRule.java index 19a15312..e7ef056f 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/AllowSubscribeRule.java @@ -1,12 +1,12 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; -import static javasabr.mqtt.model.acl.Action.ALLOW; +import static javasabr.mqtt.acl.engine.model.Action.ALLOW; import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.condition.MqttUserCondition; -import javasabr.mqtt.model.acl.condition.TopicCondition; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenyPublishRule.java similarity index 64% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenyPublishRule.java index f8dbc0ab..6942b93b 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenyPublishRule.java @@ -1,12 +1,12 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; -import static javasabr.mqtt.model.acl.Action.DENY; +import static javasabr.mqtt.acl.engine.model.Action.DENY; import static javasabr.mqtt.model.acl.Operation.PUBLISH; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.condition.MqttUserCondition; -import javasabr.mqtt.model.acl.condition.TopicCondition; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenySubscribeRule.java similarity index 64% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenySubscribeRule.java index e5b16b0d..6e286e52 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/DenySubscribeRule.java @@ -1,12 +1,12 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; -import static javasabr.mqtt.model.acl.Action.DENY; +import static javasabr.mqtt.acl.engine.model.Action.DENY; import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.condition.MqttUserCondition; -import javasabr.mqtt.model.acl.condition.TopicCondition; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/Rule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java similarity index 75% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/Rule.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java index 2b643d63..737f1974 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/Rule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.model.acl.rule; +package javasabr.mqtt.acl.engine.model.rule; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.Action; +import javasabr.mqtt.acl.engine.model.Action; import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.topic.AbstractTopic; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/package-info.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/package-info.java similarity index 55% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/package-info.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/package-info.java index 66d09c77..806eea83 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/matcher/package-info.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.model.acl.matcher; +package javasabr.mqtt.acl.engine.model.rule; import org.jspecify.annotations.NullMarked; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/package-info.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/package-info.java similarity index 62% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/package-info.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/package-info.java index 7253f0dd..046878f1 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/package-info.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.model.acl; +package javasabr.mqtt.acl.engine; import org.jspecify.annotations.NullMarked; diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/service/acl/AclRulesEngineTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy similarity index 90% rename from acl-engine/src/test/groovy/javasabr/mqtt/service/acl/AclRulesEngineTest.groovy rename to acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy index ec135c84..d06ab515 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/service/acl/AclRulesEngineTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy @@ -1,20 +1,21 @@ -package javasabr.mqtt.service.acl +package javasabr.mqtt.acl.engine import javasabr.mqtt.model.MqttUser import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.condition.AllOfCondition -import javasabr.mqtt.model.acl.condition.AnyOfCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.matcher.TopicNameMatcher -import javasabr.mqtt.model.acl.rule.AllowPublishRule -import javasabr.mqtt.model.acl.rule.AllowSubscribeRule -import javasabr.mqtt.model.acl.rule.DenyPublishRule -import javasabr.mqtt.model.acl.rule.DenySubscribeRule -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.condition.AllOfCondition +import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher +import javasabr.mqtt.acl.engine.model.rule.AllowPublishRule +import javasabr.mqtt.acl.engine.model.rule.AllowSubscribeRule +import javasabr.mqtt.acl.engine.model.rule.DenyPublishRule +import javasabr.mqtt.acl.engine.model.rule.DenySubscribeRule +import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.topic.AbstractTopic import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.model.topic.TopicName +import javasabr.mqtt.service.acl.ConditionMatcherAware import javasabr.mqtt.service.acl.builder.TopicMatcherBuilder import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array @@ -23,7 +24,7 @@ import javasabr.rlib.collections.array.MutableArray import static javasabr.mqtt.model.acl.Operation.PUBLISH import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE -class AclRulesEngineTest extends UnitSpecification implements ConditionMatcherAware, TopicMatcherBuilder { +class AclEngineTest extends UnitSpecification implements ConditionMatcherAware, TopicMatcherBuilder { def "should allow or deny according rules"( String username, String clientId, String ipAddress, Operation operation, AbstractTopic topic) { @@ -93,7 +94,7 @@ class AclRulesEngineTest extends UnitSpecification implements ConditionMatcherAw ))) subscribeRules << new AllowSubscribeRule(MqttUserCondition.MATCH_ANY, TopicCondition.MATCH_ANY) and: - AclRulesEngine engine = new AclRulesEngine(rulesEnumMap) + AclEngine engine = new AclEngine(rulesEnumMap) MqttUser mqttUser = Mock(MqttUser) mqttUser.userName() >> username mqttUser.clientId() >> clientId diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/condition/ConditionTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy similarity index 81% rename from acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/condition/ConditionTest.groovy rename to acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy index ed2f4fb5..8d01305c 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/condition/ConditionTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy @@ -1,10 +1,16 @@ -package javasabr.mqtt.model.acl.condition +package javasabr.mqtt.acl.engine.model.condition +import javasabr.mqtt.acl.engine.model.condition.AllOfCondition +import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition +import javasabr.mqtt.acl.engine.model.condition.AnyUserCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.condition.UserNameCondition +import javasabr.mqtt.acl.engine.model.matcher.AnyValueMatcher import javasabr.mqtt.model.MqttUser -import javasabr.mqtt.model.acl.matcher.AnyValueMatcher -import javasabr.mqtt.model.subscription.TestMqttUser +import TestMqttUser import javasabr.mqtt.model.topic.TopicName -import javasabr.mqtt.service.acl.ConditionMatcherAware +import ConditionMatcherAware import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/matcher/MatcherTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy similarity index 98% rename from acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/matcher/MatcherTest.groovy rename to acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy index 11e1628f..b70a348e 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/matcher/MatcherTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.acl.matcher +package javasabr.mqtt.acl.engine.model.matcher import javasabr.mqtt.model.topic.TopicFilter diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/rule/RuleTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy similarity index 90% rename from acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/rule/RuleTest.groovy rename to acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy index 502c1e40..32d466a3 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/model/acl/rule/RuleTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy @@ -1,10 +1,10 @@ -package javasabr.mqtt.model.acl.rule +package javasabr.mqtt.acl.engine.model.rule import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.subscription.TestMqttUser +import TopicCondition +import TestMqttUser import javasabr.mqtt.model.topic.TopicName -import javasabr.mqtt.service.acl.ConditionMatcherAware +import ConditionMatcherAware import javasabr.mqtt.test.support.UnitSpecification import static javasabr.mqtt.model.acl.Operation.PUBLISH diff --git a/acl-groovy-dsl/build.gradle b/acl-groovy-dsl/build.gradle index ef503c19..d3246925 100644 --- a/acl-groovy-dsl/build.gradle +++ b/acl-groovy-dsl/build.gradle @@ -5,8 +5,8 @@ plugins { } dependencies { - api libs.groovy - api projects.model + api projects.aclEngine + api libs.groovy.core api libs.rlib.collections testImplementation testFixtures(projects.model) diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy index 5d4bfa9c..941f982e 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy @@ -1,9 +1,8 @@ package javasabr.mqtt.service.acl - +import javasabr.mqtt.acl.engine.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.rule.Rule -import javasabr.mqtt.model.exception.AclConfigurationException import javasabr.mqtt.service.acl.builder.AclRulesBuilder import javasabr.mqtt.service.acl.builder.RuleContainerBuilder import javasabr.rlib.collections.array.Array diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy index f12a8a5f..6d7fe093 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy @@ -1,14 +1,14 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.rlib.collections.array.Array import javasabr.rlib.collections.array.ArrayFactory import java.util.concurrent.CompletableFuture /** - * Builds list of {@link javasabr.mqtt.model.acl.rule.Rule} from ACL configuration + * Builds list of {@link Rule} from ACL configuration */ class AclRulesBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy index 94f42e40..97b7b995 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy @@ -1,9 +1,9 @@ package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.condition.AllOfCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.matcher.ValueMatcher -import javasabr.mqtt.model.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.model.condition.AllOfCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.rlib.collections.array.Array class AllOfBuilder extends ConditionBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy index 1b3d45ac..0f3979ca 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy @@ -1,10 +1,10 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.rule.AllowPublishRule -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.rule.AllowPublishRule +import javasabr.mqtt.acl.engine.model.rule.Rule class AllowPublishRuleBuilder extends PublishRuleBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy index 7e555648..2242e9cb 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy @@ -1,10 +1,10 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.rule.AllowSubscribeRule -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.rule.AllowSubscribeRule +import javasabr.mqtt.acl.engine.model.rule.Rule class AllowSubscribeRuleBuilder extends SubscribeRuleBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy index 7d54edaf..89773050 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy @@ -1,8 +1,8 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.condition.AnyOfCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition import javasabr.rlib.collections.array.Array class AnyOfBuilder extends ConditionBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy index 7ea12b01..1c24b819 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy @@ -1,11 +1,11 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.condition.ClientIdCondition -import javasabr.mqtt.model.acl.condition.IpAddressCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.condition.UserNameCondition -import javasabr.mqtt.model.acl.matcher.ValueMatcher +import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition +import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.UserNameCondition +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.rlib.collections.array.ArrayFactory import javasabr.rlib.collections.array.MutableArray diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy index 25db309a..56da0595 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy @@ -1,10 +1,10 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.rule.DenyPublishRule -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.rule.DenyPublishRule +import javasabr.mqtt.acl.engine.model.rule.Rule class DenyPublishRuleBuilder extends PublishRuleBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy index dd6bb609..f71f8e70 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy @@ -1,10 +1,10 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.rule.DenySubscribeRule -import javasabr.mqtt.model.acl.rule.Rule +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.rule.DenySubscribeRule +import javasabr.mqtt.acl.engine.model.rule.Rule class DenySubscribeRuleBuilder extends SubscribeRuleBuilder { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy index a15b8722..28a358cc 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy @@ -1,9 +1,9 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.matcher.ValueMatcher import javasabr.mqtt.model.topic.TopicName import javasabr.rlib.collections.array.ArrayFactory import javasabr.rlib.collections.array.MutableArray diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy index 84e91895..acff4942 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy @@ -1,12 +1,12 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action +import javasabr.mqtt.acl.engine.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher +import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.matcher.ValueMatcher -import javasabr.mqtt.model.acl.rule.Rule -import javasabr.mqtt.model.exception.AclConfigurationException abstract class RuleBuilder implements TopicMatcherBuilder { Action action diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy index 3148273b..b37aab39 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy @@ -1,9 +1,9 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder -import javasabr.mqtt.model.acl.Action +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.model.acl.matcher.ValueMatcher import javasabr.mqtt.model.topic.TopicFilter import javasabr.rlib.collections.array.ArrayFactory import javasabr.rlib.collections.array.MutableArray diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/Action.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/Action.java deleted file mode 100644 index ee6feda2..00000000 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/Action.java +++ /dev/null @@ -1,5 +0,0 @@ -package javasabr.mqtt.model.acl; - -public enum Action { - ALLOW, DENY -} diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/package-info.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/package-info.java deleted file mode 100644 index dbab8ed6..00000000 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package javasabr.mqtt.model.acl.rule; - -import org.jspecify.annotations.NullMarked; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java index bf710aed..4a22ca7e 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java @@ -2,9 +2,9 @@ package javasabr.mqtt.service.acl.builder; import java.util.regex.Pattern; -import javasabr.mqtt.model.acl.matcher.EqualsMatcher; -import javasabr.mqtt.model.acl.matcher.RegexMatcher; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher; +import javasabr.mqtt.acl.engine.model.matcher.RegexMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; public interface ClientMatcherBuilder { diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java index 354d3ea9..a60950fa 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java @@ -3,8 +3,8 @@ import java.util.Collections; import java.util.EnumMap; import java.util.Map; +import javasabr.mqtt.acl.engine.model.rule.Rule; import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.acl.rule.Rule; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java index 4743ec9c..51d662c3 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java @@ -1,9 +1,9 @@ //file:noinspection unused package javasabr.mqtt.service.acl.builder; -import javasabr.mqtt.model.acl.matcher.TopicFilterMatcher; -import javasabr.mqtt.model.acl.matcher.TopicNameMatcher; -import javasabr.mqtt.model.acl.matcher.ValueMatcher; +import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher; +import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher; +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; import javasabr.mqtt.model.topic.AbstractTopic; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy index 5d6d233e..3706387e 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy +++ b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy @@ -1,26 +1,28 @@ package javasabr.mqtt.service.acl -import javasabr.mqtt.model.acl.condition.AllOfCondition -import javasabr.mqtt.model.acl.condition.AnyOfCondition -import javasabr.mqtt.model.acl.condition.ClientIdCondition -import javasabr.mqtt.model.acl.condition.Condition -import javasabr.mqtt.model.acl.condition.IpAddressCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.condition.UserNameCondition -import javasabr.mqtt.model.acl.matcher.EqualsMatcher -import javasabr.mqtt.model.acl.matcher.RegexMatcher -import javasabr.mqtt.model.acl.matcher.TopicFilterMatcher -import javasabr.mqtt.model.acl.matcher.TopicNameMatcher -import javasabr.mqtt.model.acl.matcher.ValueMatcher -import javasabr.mqtt.model.acl.rule.AbstractRule -import javasabr.mqtt.model.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.exception.AclConfigurationException +import javasabr.mqtt.acl.engine.model.Action +import javasabr.mqtt.acl.engine.model.condition.AllOfCondition +import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition +import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition +import javasabr.mqtt.acl.engine.model.condition.Condition +import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.UserNameCondition +import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher +import javasabr.mqtt.acl.engine.model.matcher.RegexMatcher +import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher +import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher +import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher +import javasabr.mqtt.acl.engine.model.rule.AbstractRule +import javasabr.mqtt.model.acl.Operation import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array import java.util.concurrent.CompletionException -import static javasabr.mqtt.model.acl.Action.ALLOW -import static javasabr.mqtt.model.acl.Action.DENY +import static javasabr.mqtt.acl.engine.model.Action.ALLOW +import static javasabr.mqtt.acl.engine.model.Action.DENY import static javasabr.mqtt.model.acl.Operation.PUBLISH import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE diff --git a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy b/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy index 0e209bd8..d0de7675 100644 --- a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy +++ b/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy @@ -1,13 +1,13 @@ package javasabr.mqtt.service.acl -import javasabr.mqtt.model.acl.condition.ClientIdCondition -import javasabr.mqtt.model.acl.condition.IpAddressCondition -import javasabr.mqtt.model.acl.condition.MqttUserCondition -import javasabr.mqtt.model.acl.condition.TopicCondition -import javasabr.mqtt.model.acl.condition.UserNameCondition -import javasabr.mqtt.model.acl.matcher.EqualsMatcher -import javasabr.mqtt.model.acl.matcher.TopicFilterMatcher -import javasabr.mqtt.model.acl.matcher.TopicNameMatcher +import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition +import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition +import javasabr.mqtt.acl.engine.model.condition.TopicCondition +import javasabr.mqtt.acl.engine.model.condition.UserNameCondition +import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher +import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher +import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.model.topic.TopicName import javasabr.mqtt.service.acl.builder.ClientMatcherBuilder diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9567a4ec..2b3f9dc7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,7 +51,7 @@ lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } spring-test = { module = "org.springframework:spring-test", version.ref = "spring" } spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } spock-spring = { module = "org.spockframework:spock-spring", version.ref = "spock" } -groovy = { module = "org.apache.groovy:groovy", version.ref = "groovy" } +groovy-core = { module = "org.apache.groovy:groovy", version.ref = "groovy" } groovy-all = { module = "org.apache.groovy:groovy-all", version.ref = "groovy" } byte-buddy-dep = { module = "net.bytebuddy:byte-buddy-dep", version.ref = "byte-buddy" } objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" } From 8fe507cd8da4a1bc7e97fd40dd5144cc13e80bb2 Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 12:55:35 +0100 Subject: [PATCH 013/107] reorganize ACL modules --- .../dsl}/builder/AclRulesBuilder.groovy | 2 +- .../groovy/dsl}/builder/AllOfBuilder.groovy | 2 +- .../builder/AllowPublishRuleBuilder.groovy | 2 +- .../builder/AllowSubscribeRuleBuilder.groovy | 2 +- .../groovy/dsl}/builder/AnyOfBuilder.groovy | 2 +- .../dsl}/builder/ConditionBuilder.groovy | 3 +- .../builder/DenyPublishRuleBuilder.groovy | 2 +- .../builder/DenySubscribeRuleBuilder.groovy | 2 +- .../dsl}/builder/PublishRuleBuilder.groovy | 2 +- .../groovy/dsl}/builder/RuleBuilder.groovy | 3 +- .../dsl}/builder/SubscribeRuleBuilder.groovy | 2 +- .../groovy/dsl/loader}/AclRulesLoader.groovy | 4 +- acl-groovy-dsl/src/main/resources/acl.gdsl | 68 +++++++++---------- .../dsl/loader}/AclRulesLoaderTest.groovy | 7 +- 14 files changed, 52 insertions(+), 51 deletions(-) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/AclRulesBuilder.groovy (97%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/AllOfBuilder.groovy (96%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/AllowPublishRuleBuilder.groovy (91%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/AllowSubscribeRuleBuilder.groovy (91%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/AnyOfBuilder.groovy (93%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/ConditionBuilder.groovy (92%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/DenyPublishRuleBuilder.groovy (91%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/DenySubscribeRuleBuilder.groovy (91%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/PublishRuleBuilder.groovy (93%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/RuleBuilder.groovy (92%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl}/builder/SubscribeRuleBuilder.groovy (94%) rename acl-groovy-dsl/src/main/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl/loader}/AclRulesLoader.groovy (93%) rename acl-groovy-dsl/src/test/groovy/javasabr/mqtt/{service/acl => acl/groovy/dsl/loader}/AclRulesLoaderTest.groovy (97%) diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AclRulesBuilder.groovy similarity index 97% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AclRulesBuilder.groovy index 6d7fe093..f6c47886 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AclRulesBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AclRulesBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.rlib.collections.array.Array diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllOfBuilder.groovy similarity index 96% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllOfBuilder.groovy index 97b7b995..1ea98459 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllOfBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllOfBuilder.groovy @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.condition.AllOfCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowPublishRuleBuilder.groovy similarity index 91% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowPublishRuleBuilder.groovy index 0f3979ca..19125f91 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowPublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowPublishRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.TopicCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowSubscribeRuleBuilder.groovy similarity index 91% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowSubscribeRuleBuilder.groovy index 2242e9cb..b380bdac 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AllowSubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AllowSubscribeRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.TopicCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AnyOfBuilder.groovy similarity index 93% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AnyOfBuilder.groovy index 89773050..e79189cd 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/AnyOfBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/AnyOfBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy similarity index 92% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy index 1c24b819..67a63c4a 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/ConditionBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy @@ -1,11 +1,12 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition import javasabr.mqtt.acl.engine.model.condition.UserNameCondition import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher +import javasabr.mqtt.service.acl.builder.ClientMatcherBuilder import javasabr.rlib.collections.array.ArrayFactory import javasabr.rlib.collections.array.MutableArray diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenyPublishRuleBuilder.groovy similarity index 91% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenyPublishRuleBuilder.groovy index 56da0595..08f2a512 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenyPublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenyPublishRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.TopicCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenySubscribeRuleBuilder.groovy similarity index 91% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenySubscribeRuleBuilder.groovy index f71f8e70..82874649 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/DenySubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/DenySubscribeRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.TopicCondition diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/PublishRuleBuilder.groovy similarity index 93% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/PublishRuleBuilder.groovy index 28a358cc..a41ffb22 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/PublishRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/PublishRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy similarity index 92% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy index acff4942..7086858e 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/RuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.Action @@ -7,6 +7,7 @@ import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.acl.Operation +import javasabr.mqtt.service.acl.builder.TopicMatcherBuilder abstract class RuleBuilder implements TopicMatcherBuilder { Action action diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/SubscribeRuleBuilder.groovy similarity index 94% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/SubscribeRuleBuilder.groovy index b37aab39..603bd913 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/builder/SubscribeRuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/SubscribeRuleBuilder.groovy @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder +package javasabr.mqtt.acl.groovy.dsl.builder import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy similarity index 93% rename from acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy rename to acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy index 941f982e..7c37405f 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy @@ -1,9 +1,9 @@ -package javasabr.mqtt.service.acl +package javasabr.mqtt.acl.groovy.dsl.loader import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.service.acl.builder.AclRulesBuilder +import javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder import javasabr.mqtt.service.acl.builder.RuleContainerBuilder import javasabr.rlib.collections.array.Array import org.codehaus.groovy.control.CompilerConfiguration diff --git a/acl-groovy-dsl/src/main/resources/acl.gdsl b/acl-groovy-dsl/src/main/resources/acl.gdsl index c564ff32..9da036ed 100644 --- a/acl-groovy-dsl/src/main/resources/acl.gdsl +++ b/acl-groovy-dsl/src/main/resources/acl.gdsl @@ -1,30 +1,30 @@ contributor(context(scope: scriptScope())) { method name: 'allowPublish', - type: 'javasabr.mqtt.service.acl.builder.AclRulesBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.PublishRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.PublishRuleBuilder', doc: 'Define Access Control List rule' method name: 'denyPublish', - type: 'javasabr.mqtt.service.acl.builder.AclRulesBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.PublishRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.PublishRuleBuilder', doc: 'Define Access Control List rule' method name: 'allowSubscribe', - type: 'javasabr.mqtt.service.acl.builder.AclRulesBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.SubscribeRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.SubscribeRuleBuilder', doc: 'Define Access Control List rule' method name: 'denySubscribe', - type: 'javasabr.mqtt.service.acl.builder.AclRulesBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.SubscribeRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.SubscribeRuleBuilder', doc: 'Define Access Control List rule' if ((enclosingCall("allowPublish") || enclosingCall("denyPublish")) && !enclosingCall("allOf") && !enclosingCall("anyOf")) { method name: 'topicName', - type: 'javasabr.mqtt.service.acl.builder.PublishRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.PublishRuleBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher...'], doc: 'Set of topic names matching by rule' } @@ -32,7 +32,7 @@ contributor(context(scope: scriptScope())) { if ((enclosingCall("allowSubscribe") || enclosingCall("denySubscribe")) && !enclosingCall("allOf") && !enclosingCall("anyOf")) { method name: 'topicFilter', - type: 'javasabr.mqtt.service.acl.builder.SubscribeRuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.SubscribeRuleBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher...'], doc: 'Set of topic filters matching by rule' method name: 'match', @@ -63,60 +63,60 @@ contributor(context(scope: scriptScope())) { || enclosingCall("allowSubscribe") || enclosingCall("denySubscribe"))) { method name: 'allOf', - type: 'javasabr.mqtt.service.acl.builder.RuleBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.RuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', doc: 'All of conditions match an incoming client' method name: 'anyOf', - type: 'javasabr.mqtt.service.acl.builder.RuleBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.RuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', doc: 'At least one condition matches an incoming client' method name: 'anyOf', - type: 'javasabr.mqtt.service.acl.builder.RuleBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', params: [:], - delegateType: 'javasabr.mqtt.service.acl.builder.RuleBuilder', + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.RuleBuilder', doc: 'Match any incoming client' } if (!enclosingCall("allOf") && enclosingCall("anyOf")) { method name: 'allOf', - type: 'javasabr.mqtt.service.acl.builder.AllOfBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AllOfBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', doc: 'All of conditions match an incoming client' method name: 'anyOf', - type: 'javasabr.mqtt.service.acl.builder.AnyOfBuilder', - params: [config: 'Closure'], - delegateType: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.AnyOfBuilder', + params: [config: 'Closure'], + delegateType: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', doc: 'At least one condition matches an incoming client' } if (enclosingCall("allOf")) { method name: 'userName', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher'], doc: 'Match by username' method name: 'clientId', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher'], doc: 'Match by clientId' method name: 'ipAddress', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher'], doc: 'Match by IP address' } if (enclosingCall("anyOf") && !enclosingCall("allOf")) { method name: 'userName', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher...'], doc: 'Match by username' method name: 'clientId', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher...'], doc: 'Match by clientId' method name: 'ipAddress', - type: 'javasabr.mqtt.service.acl.builder.ConditionBuilder', + type: 'javasabr.mqtt.acl.groovy.dsl.builder.ConditionBuilder', params: [string: 'javasabr.mqtt.model.acl.matcher.ValueMatcher...'], doc: 'Match by IP address' } diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy similarity index 97% rename from acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy rename to acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy index 3706387e..25d36d6f 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/service/acl/AclRulesLoaderTest.groovy +++ b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy @@ -1,7 +1,6 @@ -package javasabr.mqtt.service.acl +package javasabr.mqtt.acl.groovy.dsl.loader import javasabr.mqtt.acl.engine.exception.AclConfigurationException -import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.AllOfCondition import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition @@ -15,7 +14,7 @@ import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.mqtt.acl.engine.model.rule.AbstractRule -import javasabr.mqtt.model.acl.Operation +import javasabr.mqtt.service.acl.TestRulesGenerator import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array @@ -78,7 +77,7 @@ class AclRulesLoaderTest extends UnitSpecification { "1.config" | AclConfigurationException | 'Only one clients section allowed' "2.config" | AclConfigurationException | 'Only one clients section allowed' "3.config" | AclConfigurationException | 'AllOf condition can only have single-matcher members' - "4.config" | MissingMethodException | 'No signature of method: javasabr.mqtt.service.acl.builder.AllOfBuilder.allOf' + "4.config" | MissingMethodException | 'No signature of method: javasabr.mqtt.acl.groovy.dsl.builder.AllOfBuilder.allOf' "5.config" | AclConfigurationException | 'AllOf condition can only have single-matcher members' } From 5bcdcd1691c5c2e7780bf2ded6949b1525754a11 Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 13:06:45 +0100 Subject: [PATCH 014/107] reorganize ACL modules --- acl-engine/build.gradle | 1 + .../javasabr/mqtt/acl/engine/AclEngine.java | 4 +- .../engine}/builder/ClientMatcherBuilder.java | 2 +- .../engine}/builder/RuleContainerBuilder.java | 2 +- .../engine}/builder/TopicMatcherBuilder.java | 2 +- .../model/condition/ClientIdCondition.java | 2 +- .../model/condition/IpAddressCondition.java | 2 +- .../model/condition/UserNameCondition.java | 2 +- .../mqtt/acl/engine/model/rule/Rule.java | 2 +- .../mqtt/acl/engine/AclEngineTest.groovy | 7 ++- .../model/condition/ConditionTest.groovy | 12 ++--- .../engine/model/matcher/MatcherTest.groovy | 1 - .../acl/engine/model/rule/RuleTest.groovy | 8 +-- .../acl/engine/ConditionMatcherAware.java | 50 +++++++++++++++++++ .../dsl/builder/ConditionBuilder.groovy | 2 +- .../acl/groovy/dsl/builder/RuleBuilder.groovy | 2 +- .../groovy/dsl/loader/AclRulesLoader.groovy | 4 +- .../service/acl/ConditionMatcherAware.groovy | 37 -------------- .../service/acl/TestRulesGenerator.groovy | 2 +- 19 files changed, 75 insertions(+), 69 deletions(-) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine}/builder/ClientMatcherBuilder.java (92%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine}/builder/RuleContainerBuilder.java (97%) rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl => acl-engine/src/main/java/javasabr/mqtt/acl/engine}/builder/TopicMatcherBuilder.java (93%) create mode 100644 acl-engine/src/testFixtures/java/javasabr/mqtt/acl/engine/ConditionMatcherAware.java delete mode 100644 acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy diff --git a/acl-engine/build.gradle b/acl-engine/build.gradle index 5c90514a..1045b3d6 100644 --- a/acl-engine/build.gradle +++ b/acl-engine/build.gradle @@ -8,4 +8,5 @@ dependencies { api projects.model testImplementation projects.testSupport + testImplementation testFixtures(projects.model) } diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java index 67cd6884..8f2fd9f7 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java @@ -1,10 +1,10 @@ package javasabr.mqtt.acl.engine; import java.util.Map; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.acl.engine.model.Action; -import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.acl.engine.model.rule.Rule; +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.topic.AbstractTopic; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/ClientMatcherBuilder.java similarity index 92% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/ClientMatcherBuilder.java index 4a22ca7e..124daccf 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/ClientMatcherBuilder.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/ClientMatcherBuilder.java @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder; +package javasabr.mqtt.acl.engine.builder; import java.util.regex.Pattern; import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java similarity index 97% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java index a60950fa..03f8be59 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/RuleContainerBuilder.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.acl.builder; +package javasabr.mqtt.acl.engine.builder; import java.util.Collections; import java.util.EnumMap; diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/TopicMatcherBuilder.java similarity index 93% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java rename to acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/TopicMatcherBuilder.java index 51d662c3..e3ef243c 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/TopicMatcherBuilder.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/TopicMatcherBuilder.java @@ -1,5 +1,5 @@ //file:noinspection unused -package javasabr.mqtt.service.acl.builder; +package javasabr.mqtt.acl.engine.builder; import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher; import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher; diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java index 01808632..7986f944 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/ClientIdCondition.java @@ -1,7 +1,7 @@ package javasabr.mqtt.acl.engine.model.condition; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; +import javasabr.mqtt.model.MqttUser; public record ClientIdCondition(ValueMatcher expectedClientId) implements MqttUserCondition { diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java index 07642291..1f490e08 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/IpAddressCondition.java @@ -1,7 +1,7 @@ package javasabr.mqtt.acl.engine.model.condition; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; +import javasabr.mqtt.model.MqttUser; public record IpAddressCondition(ValueMatcher expectedIpAddress) implements MqttUserCondition { diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java index fc08667b..b4129007 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/condition/UserNameCondition.java @@ -1,7 +1,7 @@ package javasabr.mqtt.acl.engine.model.condition; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher; +import javasabr.mqtt.model.MqttUser; public record UserNameCondition(ValueMatcher userNameMatcher) implements MqttUserCondition { diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java index 737f1974..9cb2154a 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/model/rule/Rule.java @@ -1,7 +1,7 @@ package javasabr.mqtt.acl.engine.model.rule; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.acl.engine.model.Action; +import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.topic.AbstractTopic; diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy index d06ab515..cfe8fe46 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/AclEngineTest.groovy @@ -1,7 +1,6 @@ package javasabr.mqtt.acl.engine -import javasabr.mqtt.model.MqttUser -import javasabr.mqtt.model.acl.Operation +import javasabr.mqtt.acl.engine.builder.TopicMatcherBuilder import javasabr.mqtt.acl.engine.model.condition.AllOfCondition import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition @@ -12,11 +11,11 @@ import javasabr.mqtt.acl.engine.model.rule.AllowSubscribeRule import javasabr.mqtt.acl.engine.model.rule.DenyPublishRule import javasabr.mqtt.acl.engine.model.rule.DenySubscribeRule import javasabr.mqtt.acl.engine.model.rule.Rule +import javasabr.mqtt.model.MqttUser +import javasabr.mqtt.model.acl.Operation import javasabr.mqtt.model.topic.AbstractTopic import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.model.topic.TopicName -import javasabr.mqtt.service.acl.ConditionMatcherAware -import javasabr.mqtt.service.acl.builder.TopicMatcherBuilder import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array import javasabr.rlib.collections.array.MutableArray diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy index 8d01305c..b0e12ee3 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy @@ -1,16 +1,10 @@ package javasabr.mqtt.acl.engine.model.condition -import javasabr.mqtt.acl.engine.model.condition.AllOfCondition -import javasabr.mqtt.acl.engine.model.condition.AnyOfCondition -import javasabr.mqtt.acl.engine.model.condition.AnyUserCondition -import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition -import javasabr.mqtt.acl.engine.model.condition.TopicCondition -import javasabr.mqtt.acl.engine.model.condition.UserNameCondition +import javasabr.mqtt.acl.engine.ConditionMatcherAware import javasabr.mqtt.acl.engine.model.matcher.AnyValueMatcher import javasabr.mqtt.model.MqttUser -import TestMqttUser +import javasabr.mqtt.model.subscription.TestMqttUser import javasabr.mqtt.model.topic.TopicName -import ConditionMatcherAware import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array @@ -23,7 +17,7 @@ class ConditionTest extends UnitSpecification implements ConditionMatcherAware { result == expectedResult where: mqttUser | condition | expectedResult - new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username2")) | true + new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username2")) | true new TestMqttUser(null, "username2", null) | new AllOfCondition(userNameEquals("username2")) | true new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username1")) | false new TestMqttUser(null, "username2", null) | new AllOfCondition(userNameEquals("username1")) | false diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy index b70a348e..0bddbf8d 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/matcher/MatcherTest.groovy @@ -1,6 +1,5 @@ package javasabr.mqtt.acl.engine.model.matcher - import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.test.support.UnitSpecification diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy index 32d466a3..c887bd04 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy @@ -1,10 +1,10 @@ package javasabr.mqtt.acl.engine.model.rule +import javasabr.mqtt.acl.engine.ConditionMatcherAware +import javasabr.mqtt.acl.engine.model.condition.TopicCondition import javasabr.mqtt.model.acl.Operation -import TopicCondition -import TestMqttUser +import javasabr.mqtt.model.subscription.TestMqttUser import javasabr.mqtt.model.topic.TopicName -import ConditionMatcherAware import javasabr.mqtt.test.support.UnitSpecification import static javasabr.mqtt.model.acl.Operation.PUBLISH @@ -26,7 +26,7 @@ class RuleTest extends UnitSpecification implements ConditionMatcherAware { SUBSCRIBE | new AllowPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false PUBLISH | new AllowSubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false SUBSCRIBE | new AllowSubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true - PUBLISH | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true + PUBLISH | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true SUBSCRIBE | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false PUBLISH | new DenySubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false SUBSCRIBE | new DenySubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true diff --git a/acl-engine/src/testFixtures/java/javasabr/mqtt/acl/engine/ConditionMatcherAware.java b/acl-engine/src/testFixtures/java/javasabr/mqtt/acl/engine/ConditionMatcherAware.java new file mode 100644 index 00000000..a3979c0c --- /dev/null +++ b/acl-engine/src/testFixtures/java/javasabr/mqtt/acl/engine/ConditionMatcherAware.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.acl.engine; + +import javasabr.mqtt.acl.engine.builder.ClientMatcherBuilder; +import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition; +import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition; +import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition; +import javasabr.mqtt.acl.engine.model.condition.TopicCondition; +import javasabr.mqtt.acl.engine.model.condition.UserNameCondition; +import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher; +import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher; +import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.rlib.collections.array.Array; + +public interface ConditionMatcherAware extends ClientMatcherBuilder { + + default MqttUserCondition userNameEquals(String value) { + return new UserNameCondition(new EqualsMatcher(value)); + } + + default MqttUserCondition userNameRegex(String value) { + return new UserNameCondition(regex(value)); + } + + default MqttUserCondition clientIdEquals(String value) { + return new ClientIdCondition(new EqualsMatcher(value)); + } + + default MqttUserCondition clientIdRegex(String value) { + return new ClientIdCondition(regex(value)); + } + + default MqttUserCondition ipAddressEquals(String value) { + return new IpAddressCondition(new EqualsMatcher(value)); + } + + default MqttUserCondition ipAddressRegex(String value) { + return new IpAddressCondition(regex(value)); + } + + default TopicCondition topicNameCondition(String value) { + return new TopicCondition(Array.of(new TopicNameMatcher(TopicName.valueOf(value)))); + } + + default TopicCondition topicFilterCondition(String value) { + return new TopicCondition(Array.of(new TopicFilterMatcher(TopicFilter.valueOf(value)))); + } +} + diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy index 67a63c4a..ed6fabb2 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/ConditionBuilder.groovy @@ -1,12 +1,12 @@ //file:noinspection unused package javasabr.mqtt.acl.groovy.dsl.builder +import javasabr.mqtt.acl.engine.builder.ClientMatcherBuilder import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition import javasabr.mqtt.acl.engine.model.condition.UserNameCondition import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher -import javasabr.mqtt.service.acl.builder.ClientMatcherBuilder import javasabr.rlib.collections.array.ArrayFactory import javasabr.rlib.collections.array.MutableArray diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy index 7086858e..9a6e3177 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/builder/RuleBuilder.groovy @@ -1,13 +1,13 @@ //file:noinspection unused package javasabr.mqtt.acl.groovy.dsl.builder +import javasabr.mqtt.acl.engine.builder.TopicMatcherBuilder import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.Action import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition import javasabr.mqtt.acl.engine.model.matcher.ValueMatcher import javasabr.mqtt.acl.engine.model.rule.Rule import javasabr.mqtt.model.acl.Operation -import javasabr.mqtt.service.acl.builder.TopicMatcherBuilder abstract class RuleBuilder implements TopicMatcherBuilder { Action action diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy index 7c37405f..75d555a5 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy @@ -1,10 +1,10 @@ package javasabr.mqtt.acl.groovy.dsl.loader +import javasabr.mqtt.acl.engine.builder.RuleContainerBuilder import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.rule.Rule -import javasabr.mqtt.model.acl.Operation import javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder -import javasabr.mqtt.service.acl.builder.RuleContainerBuilder +import javasabr.mqtt.model.acl.Operation import javasabr.rlib.collections.array.Array import org.codehaus.groovy.control.CompilerConfiguration diff --git a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy b/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy deleted file mode 100644 index d0de7675..00000000 --- a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/ConditionMatcherAware.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package javasabr.mqtt.service.acl - -import javasabr.mqtt.acl.engine.model.condition.ClientIdCondition -import javasabr.mqtt.acl.engine.model.condition.IpAddressCondition -import javasabr.mqtt.acl.engine.model.condition.MqttUserCondition -import javasabr.mqtt.acl.engine.model.condition.TopicCondition -import javasabr.mqtt.acl.engine.model.condition.UserNameCondition -import javasabr.mqtt.acl.engine.model.matcher.EqualsMatcher -import javasabr.mqtt.acl.engine.model.matcher.TopicFilterMatcher -import javasabr.mqtt.acl.engine.model.matcher.TopicNameMatcher -import javasabr.mqtt.model.topic.TopicFilter -import javasabr.mqtt.model.topic.TopicName -import javasabr.mqtt.service.acl.builder.ClientMatcherBuilder -import javasabr.rlib.collections.array.Array - -interface ConditionMatcherAware extends ClientMatcherBuilder { - - default MqttUserCondition userNameEquals(String value) { new UserNameCondition(new EqualsMatcher(value)) } - - default MqttUserCondition userNameRegex(String value) { new UserNameCondition(regex(value)) } - - default MqttUserCondition clientIdEquals(String value) { new ClientIdCondition(new EqualsMatcher(value)) } - - default MqttUserCondition clientIdRegex(String value) { new ClientIdCondition(regex(value)) } - - default MqttUserCondition ipAddressEquals(String value) { new IpAddressCondition(new EqualsMatcher(value)) } - - default MqttUserCondition ipAddressRegex(String value) { new IpAddressCondition(regex(value)) } - - default TopicCondition topicNameCondition(String value) { - return new TopicCondition(Array.of(new TopicNameMatcher(TopicName.valueOf(value)))); - } - - default TopicCondition topicFilterCondition(String value) { - return new TopicCondition(Array.of(new TopicFilterMatcher(TopicFilter.valueOf(value)))); - } -} diff --git a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/TestRulesGenerator.groovy b/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/TestRulesGenerator.groovy index 98e07829..fd80b7fb 100644 --- a/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/TestRulesGenerator.groovy +++ b/acl-groovy-dsl/src/testFixtures/groovy/javasabr/mqtt/service/acl/TestRulesGenerator.groovy @@ -6,7 +6,7 @@ class TestRulesGenerator { private static final SecureRandom RANDOM = new SecureRandom() - public static File generate(int ruleCount) { + static File generate(int ruleCount) { def fileName = "${RANDOM.nextLong()}.groovy" def file = new File("build/${fileName}") file.withWriter('UTF-8') { writer -> From f3fd5c7b6a6f43f4541f577dff7479e6b96ed0c8 Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 17:06:08 +0100 Subject: [PATCH 015/107] integrate configuring groovy acl for application --- .../javasabr/mqtt/acl/engine/AclEngine.java | 12 ++++ .../engine/builder/RuleContainerBuilder.java | 4 +- .../groovy/dsl/loader/AclRulesLoader.groovy | 26 ++++----- .../acl/groovy/dsl/loader/package-info.java | 4 ++ acl-service/build.gradle | 11 ++++ .../AclEngineBasedAuthorizationService.java | 33 +++++++++++ .../GroovyDslBasedAclServiceSpringConfig.java | 26 +++++++++ .../GroovyDslBasedAuthorizationService.java | 56 +++++++++++++++++++ .../mqtt/acl/service}/package-info.java | 2 +- application/build.gradle | 2 + .../config/MqttBrokerSpringConfig.java | 18 +++++- ...plication.properties => broker.properties} | 1 - .../mqtt/service/AuthorizationService.java | 4 +- .../impl/DisabledAuthorizationService.java | 4 +- gradle/libs.versions.toml | 7 ++- settings.gradle | 1 + 16 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/package-info.java create mode 100644 acl-service/build.gradle create mode 100644 acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java create mode 100644 acl-service/src/main/java/javasabr/mqtt/acl/service/conifg/GroovyDslBasedAclServiceSpringConfig.java create mode 100644 acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java rename {acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder => acl-service/src/main/java/javasabr/mqtt/acl/service}/package-info.java (57%) rename application/src/main/resources/{application.properties => broker.properties} (97%) diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java index 8f2fd9f7..8770770c 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/AclEngine.java @@ -1,6 +1,9 @@ package javasabr.mqtt.acl.engine; +import java.util.Arrays; +import java.util.EnumMap; import java.util.Map; +import java.util.stream.Collectors; import javasabr.mqtt.acl.engine.model.Action; import javasabr.mqtt.acl.engine.model.rule.Rule; import javasabr.mqtt.model.MqttUser; @@ -15,6 +18,15 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public final class AclEngine { + public static final AclEngine NO_OPS_ENGINE; + + static { + Map> emptyRules = Arrays + .stream(Operation.values()) + .collect(Collectors.toMap(operation -> operation, _ -> Array.empty(Rule.class))); + NO_OPS_ENGINE = new AclEngine(new EnumMap<>(emptyRules)); + } + Map> ruleMap; public boolean authorize(MqttUser mqttUser, Operation operation, AbstractTopic topic) { diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java index 03f8be59..b1929a2f 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/builder/RuleContainerBuilder.java @@ -17,7 +17,6 @@ public final class RuleContainerBuilder { private static final Array EMPTY_RULES = Array.empty(Rule.class); public static Map> groupRulesByOperation(Array rules) { - var intermediate = new EnumMap>(Operation.class); for (Rule rule : rules) { intermediate @@ -31,7 +30,8 @@ public static Map> groupRulesByOperation(Array rule Operation.forEach(operation -> { finalMap.computeIfAbsent(operation, RuleContainerBuilder::emptyArray); }); - return Collections.unmodifiableMap(finalMap); + // no need to wrap this map because it's not public API + return finalMap; } static Array emptyArray(Operation ignored) { diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy index 75d555a5..db806252 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy @@ -12,21 +12,15 @@ import java.nio.file.Files import java.nio.file.Path class AclRulesLoader { - - @SuppressWarnings('GrFinalVariableAccess') - private final Path aclConfigPath - - AclRulesLoader(String aclConfigPath) { - if (aclConfigPath == null) { - throw new AclConfigurationException("ACL config path is null") - } - this.aclConfigPath = Path.of(aclConfigPath) - if (Files.notExists(this.aclConfigPath)) { - throw new AclConfigurationException("Class loader unable to load resource: %s".formatted(aclConfigPath)) - } + + static Map> load(String aclConfigPath) { + return load(Path.of(aclConfigPath)) } - - Map> load() { + + static Map> load(Path aclConfigPath) { + if (Files.notExists(aclConfigPath)) { + throw new AclConfigurationException("Config file:[%s] doesn't exist".formatted(aclConfigPath)) + } CompilerConfiguration compilerConfig = new CompilerConfiguration() AclRulesBuilder aclRulesBuilder = new AclRulesBuilder() new GroovyShell(compilerConfig).with { @@ -36,7 +30,7 @@ class AclRulesLoader { setVariable("denySubscribe", aclRulesBuilder.&denySubscribe) evaluate(aclConfigPath.toFile()) } - def rules = aclRulesBuilder.build() - return RuleContainerBuilder.groupRulesByOperation(rules) + def allDefinedRules = aclRulesBuilder.build() + return RuleContainerBuilder.groupRulesByOperation(allDefinedRules) } } diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/package-info.java b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/package-info.java new file mode 100644 index 00000000..6ad16c41 --- /dev/null +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.acl.groovy.dsl.loader; + +import org.jspecify.annotations.NullMarked; diff --git a/acl-service/build.gradle b/acl-service/build.gradle new file mode 100644 index 00000000..835c7bce --- /dev/null +++ b/acl-service/build.gradle @@ -0,0 +1,11 @@ +plugins { + id("java-library") + id("configure-java") +} + +dependencies { + api projects.aclEngine + api projects.coreService + api libs.springboot.starter.autoconfigure + compileOnlyApi projects.aclGroovyDsl +} diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java new file mode 100644 index 00000000..fa2f2f83 --- /dev/null +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java @@ -0,0 +1,33 @@ +package javasabr.mqtt.acl.service; + +import javasabr.mqtt.acl.engine.AclEngine; +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.acl.Operation; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.service.AuthorizationService; + +public abstract class AclEngineBasedAuthorizationService implements AuthorizationService { + + protected AclEngine engine; + + protected AclEngineBasedAuthorizationService() { + this.engine = AclEngine.NO_OPS_ENGINE; + } + + @Override + public boolean authorizePublish(MqttUser user, TopicName topicName) { + return engine.authorize(user, Operation.PUBLISH, topicName); + } + + @Override + public boolean authorizeSubscribe(MqttUser user, TopicFilter topicFilter) { + return engine.authorize(user, Operation.SUBSCRIBE, topicFilter); + } + + // we know that writing this to not volatile field will not apply it for all threads immediately, + // but for us it's not critical comparing to cost of reading volatile field + protected synchronized void switchTo(AclEngine newEngine) { + this.engine = newEngine; + } +} diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/conifg/GroovyDslBasedAclServiceSpringConfig.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/conifg/GroovyDslBasedAclServiceSpringConfig.java new file mode 100644 index 00000000..2c3d1469 --- /dev/null +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/conifg/GroovyDslBasedAclServiceSpringConfig.java @@ -0,0 +1,26 @@ +package javasabr.mqtt.acl.service.conifg; + +import java.net.URI; +import javasabr.mqtt.acl.service.impl.GroovyDslBasedAuthorizationService; +import javasabr.mqtt.service.AuthorizationService; +import lombok.CustomLog; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@CustomLog +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "acl.engine.type", havingValue = "groovy-dsl") +@ConditionalOnClass(name = "javasabr.mqtt.acl.groovy.dsl.loader.AclRulesLoader") +public class GroovyDslBasedAclServiceSpringConfig { + + @Bean + AuthorizationService authorizationService(@Value("${acl.engine.groovy.dsl.config}") URI aclConfigUri) { + log.info("Initializing Groovy-DSL based AuthorizationService..."); + var authorizationService = new GroovyDslBasedAuthorizationService(); + authorizationService.loadFrom(aclConfigUri); + return authorizationService; + } +} diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java new file mode 100644 index 00000000..43061325 --- /dev/null +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java @@ -0,0 +1,56 @@ +package javasabr.mqtt.acl.service.impl; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import javasabr.mqtt.acl.engine.AclEngine; +import javasabr.mqtt.acl.engine.model.rule.Rule; +import javasabr.mqtt.acl.groovy.dsl.loader.AclRulesLoader; +import javasabr.mqtt.acl.service.AclEngineBasedAuthorizationService; +import javasabr.mqtt.model.acl.Operation; +import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; +import javasabr.rlib.collections.array.Array; +import lombok.CustomLog; +import org.jspecify.annotations.Nullable; + +@CustomLog +public class GroovyDslBasedAuthorizationService extends AclEngineBasedAuthorizationService { + + public void loadFrom(URI resource) { + Path localFile = Path.of(resource); + if (Files.notExists(localFile)) { + throw new IllegalArgumentException("ACL configuration:[%s] doesn't exist".formatted(resource)); + } else if (Files.isDirectory(localFile)) { + throw new IllegalArgumentException("ACL configuration:[%s] is directory".formatted(resource)); + } + Map> loadedAclRulesMap = AclRulesLoader.load(localFile); + switchTo(new AclEngine(loadedAclRulesMap)); + log.info(resource, loadedAclRulesMap, GroovyDslBasedAuthorizationService::buildServiceDescription); + } + + private static String buildServiceDescription(URI resource, Map> aclRulesMap) { + var builder = new StringBuilder(); + builder.append("{\n"); + builder.append(" \"SOURCE\":\"").append(resource).append("\",\n"); + + int count = 0; + for (Map.Entry> entry : aclRulesMap.entrySet()) { + Operation operation = entry.getKey(); + Array rules = entry.getValue(); + count += rules.size(); + builder + .append(" \"") + .append(operation.name()) + .append("\": ") + .append(rules.size()) + .append(",") + .append("\n"); + } + builder + .delete(builder.length() - 2, builder.length()) + .append("\n}"); + + return "Loaded total [%s] ACL rules: %s".formatted(count, builder); + } +} diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/package-info.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/package-info.java similarity index 57% rename from acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/package-info.java rename to acl-service/src/main/java/javasabr/mqtt/acl/service/package-info.java index 064b1ebf..bf4d06f7 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/service/acl/builder/package-info.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.service.acl.builder; +package javasabr.mqtt.acl.service; import org.jspecify.annotations.NullMarked; diff --git a/application/build.gradle b/application/build.gradle index 6658c3a8..8c1305d0 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -9,6 +9,8 @@ description = "Standard configuration of standalone version of MQTT Broker" dependencies { implementation projects.coreService + implementation projects.aclService + implementation projects.aclGroovyDsl implementation libs.rlib.logger.slf4j implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index f330b24e..59bbeeed 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -3,6 +3,7 @@ import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; +import javasabr.mqtt.acl.service.conifg.GroovyDslBasedAclServiceSpringConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -14,7 +15,6 @@ import javasabr.mqtt.network.user.NetworkMqttUserFactory; import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.AuthenticationService; -import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; import javasabr.mqtt.service.CredentialSource; @@ -71,14 +71,26 @@ import javasabr.rlib.network.server.ServerNetwork; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.PropertySources; import org.springframework.core.env.Environment; +@Import({ + GroovyDslBasedAclServiceSpringConfig.class +}) @CustomLog @Configuration(proxyBeanMethods = false) +@PropertySources({ + @PropertySource("classpath:broker.properties"), + @PropertySource(value = "file:./broker.properties", ignoreResourceNotFound = true), + @PropertySource(value = "${BROKER_CONFIG}", ignoreResourceNotFound = true) +}) public class MqttBrokerSpringConfig { @Bean @@ -110,6 +122,10 @@ AuthenticationService authenticationService( } @Bean + @ConditionalOnProperty( + name = "acl.engine.type", + havingValue = "disabled", + matchIfMissing = true) AuthorizationService authorizationService() { return new DisabledAuthorizationService(); } diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/broker.properties similarity index 97% rename from application/src/main/resources/application.properties rename to application/src/main/resources/broker.properties index 6fa79d2b..e7bb12f9 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/broker.properties @@ -1,2 +1 @@ authentication.allow.anonymous=false - diff --git a/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java b/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java index d6fbbd02..5da84af4 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java @@ -6,7 +6,7 @@ public interface AuthorizationService { - boolean authorizePublish(MqttUser mqttUser, TopicName topicName); + boolean authorizePublish(MqttUser user, TopicName topicName); - boolean authorizeSubscribe(MqttUser mqttUser, TopicFilter topicFilter); + boolean authorizeSubscribe(MqttUser user, TopicFilter topicFilter); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java index e40270ee..f3eb9cc5 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java @@ -7,12 +7,12 @@ public class DisabledAuthorizationService implements AuthorizationService { @Override - public boolean authorizePublish(MqttUser mqttUser, TopicName topicName) { + public boolean authorizePublish(MqttUser user, TopicName topicName) { return true; } @Override - public boolean authorizeSubscribe(MqttUser mqttUser, TopicFilter topicFilter) { + public boolean authorizeSubscribe(MqttUser user, TopicFilter topicFilter) { return true; } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b3f9dc7..4295313c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,9 +18,9 @@ spock = "2.4-M6-groovy-4.0" # https://mvnrepository.com/artifact/org.apache.groovy/groovy-all groovy = "4.0.28" # https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -springboot = '3.5.4' +springboot = '3.5.8' # https://mvnrepository.com/artifact/org.springframework/spring-core -spring = '6.2.9' +spring = '6.2.15' # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy byte-buddy = "1.17.6" # https://mvnrepository.com/artifact/org.objenesis/objenesis @@ -39,7 +39,10 @@ rlib-logger-api = { module = "javasabr.rlib:rlib-logger-api", version.ref = "rli rlib-logger-slf4j = { module = "javasabr.rlib:rlib-logger-slf4j", version.ref = "rlib" } rlib-logger-impl = { module = "javasabr.rlib:rlib-logger-impl", version.ref = "rlib" } rlib-collections = { module = "javasabr.rlib:rlib-collections", version.ref = "rlib" } +spring-core = { module = "org.springframework:spring-core", version.ref = "spring" } +spring-context = { module = "org.springframework:spring-context", version.ref = "spring" } springboot-starter-core = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot" } +springboot-starter-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure", version.ref = "springboot" } springboot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2", version.ref = "springboot" } project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" } jackson-core = { module = "tools.jackson.core:jackson-core", version.ref = "jackson" } diff --git a/settings.gradle b/settings.gradle index 1b6ab9f7..e2843df4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,3 +12,4 @@ include(":test-support") include(":test-coverage") include(":acl-groovy-dsl") include(":acl-engine") +include(":acl-service") From cfe73ce7f4b0371d218bf8950772035d071c041f Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 18:07:02 +0100 Subject: [PATCH 016/107] integrate configuring groovy acl for application --- .../groovy/dsl/loader/AclRulesLoaderTest.groovy | 15 ++++++--------- .../config/MqttBrokerSpringConfig.java | 4 ++-- .../src/main/resources/application.properties | 2 ++ application/src/main/resources/broker.properties | 1 - .../application/IntegrationSpecification.groovy | 3 +++ .../config/MqttBrokerTestConfig.groovy | 2 -- .../mqtt/service/impl/FileCredentialsSource.java | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 application/src/main/resources/application.properties delete mode 100644 application/src/main/resources/broker.properties diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy index 25d36d6f..07f2ef0d 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy +++ b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy @@ -31,7 +31,7 @@ class AclRulesLoaderTest extends UnitSpecification { given: def ruleFile = TestRulesGenerator.generate(100) when: - def load = new AclRulesLoader(ruleFile.toString()).load() + def load = AclRulesLoader.load(ruleFile.toString()) then: load.get(SUBSCRIBE).size() == 50 load.get(PUBLISH).size() == 50 @@ -40,22 +40,20 @@ class AclRulesLoaderTest extends UnitSpecification { def "should throw exception if config not exists"(String configPath, String errorMessage) { when: - new AclRulesLoader(configPath) + AclRulesLoader.load(configPath) then: def exception = thrown(AclConfigurationException) exception.message == errorMessage where: configPath | errorMessage - "not/existed/path" | 'Class loader unable to load resource: not/existed/path' - null | 'ACL config path is null' + "not/existed/path" | 'Config file:[not/existed/path] doesn\'t exist' } def "should work fine with only publish rules"() { given: def onlyPublishRulesAclPath = getAbsolutePath("acl/config/acl-publish-only.groovy") - def rules = new AclRulesLoader(onlyPublishRulesAclPath) when: - def ruleMap = rules.load() + def ruleMap = AclRulesLoader.load(onlyPublishRulesAclPath) then: noExceptionThrown() !ruleMap.get(PUBLISH).isEmpty() @@ -65,9 +63,8 @@ class AclRulesLoaderTest extends UnitSpecification { def "should throw exception if config is invalid"(String invalidAclFileName, String errorMessage, Class exceptionClass) { given: def invalidAclPath = getAbsolutePath("acl/config/invalid/${invalidAclFileName}") - def rules = new AclRulesLoader(invalidAclPath) when: - rules.load() + AclRulesLoader.load(invalidAclPath) then: def exception = thrown CompletionException exceptionClass.isInstance exception.cause @@ -89,7 +86,7 @@ class AclRulesLoaderTest extends UnitSpecification { def "should parse Groovy DSL config"() { when: def absolutePath = getAbsolutePath("acl/config/acl.groovy") - def rules = new AclRulesLoader(absolutePath).load() + def rules = AclRulesLoader.load(absolutePath) then: verifyAll(rules.get(PUBLISH)) { size() == 2 diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 59bbeeed..fa8f992e 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -87,8 +87,8 @@ @CustomLog @Configuration(proxyBeanMethods = false) @PropertySources({ - @PropertySource("classpath:broker.properties"), - @PropertySource(value = "file:./broker.properties", ignoreResourceNotFound = true), + @PropertySource("classpath:application.properties"), + @PropertySource(value = "file:./application.properties", ignoreResourceNotFound = true), @PropertySource(value = "${BROKER_CONFIG}", ignoreResourceNotFound = true) }) public class MqttBrokerSpringConfig { diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties new file mode 100644 index 00000000..d5a70fdc --- /dev/null +++ b/application/src/main/resources/application.properties @@ -0,0 +1,2 @@ +authentication.allow.anonymous=false +credentials.source.file.name=credentials diff --git a/application/src/main/resources/broker.properties b/application/src/main/resources/broker.properties deleted file mode 100644 index e7bb12f9..00000000 --- a/application/src/main/resources/broker.properties +++ /dev/null @@ -1 +0,0 @@ -authentication.allow.anonymous=false diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy index 70bf8dde..ea339ce3 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy @@ -12,6 +12,8 @@ import javasabr.mqtt.network.MqttConnection import javasabr.mqtt.network.MqttMockClient import javasabr.mqtt.network.user.ConfigurableNetworkMqttUser import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import spock.lang.Specification @@ -19,6 +21,7 @@ import java.nio.charset.StandardCharsets import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference +@TestPropertySource("classpath:application-test.properties") @SpringJUnitConfig(classes = MqttBrokerTestConfig) class IntegrationSpecification extends Specification { diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy index 35b6f927..2d5a18c6 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy @@ -8,7 +8,6 @@ import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import -import org.springframework.context.annotation.PropertySource import java.util.concurrent.ThreadLocalRandom @@ -16,7 +15,6 @@ import java.util.concurrent.ThreadLocalRandom MqttBrokerSpringConfig, ]) @Configuration(proxyBeanMethods = false) -@PropertySource("classpath:application-test.properties") class MqttBrokerTestConfig { @Bean diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java b/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java index 589d11b3..f7dd450f 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/FileCredentialsSource.java @@ -25,7 +25,7 @@ void init() { .getResource(fileName); if (credentialUrl == null) { - throw new CredentialsSourceException("Credentials file could not be found"); + throw new CredentialsSourceException("Credentials file:[%s] could not be found".formatted(fileName)); } try { From 63c300c5f1eed4cb171ad8e69663ee29a1b24eff Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 14 Dec 2025 19:40:20 +0100 Subject: [PATCH 017/107] resolve review comments --- .../mqtt/acl/engine/model/condition/ConditionTest.groovy | 2 +- .../groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy index b0e12ee3..b5d5bab2 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/condition/ConditionTest.groovy @@ -17,7 +17,7 @@ class ConditionTest extends UnitSpecification implements ConditionMatcherAware { result == expectedResult where: mqttUser | condition | expectedResult - new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username2")) | true + new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username2")) | true new TestMqttUser(null, "username2", null) | new AllOfCondition(userNameEquals("username2")) | true new TestMqttUser(null, "username2", null) | new AnyOfCondition(userNameEquals("username1")) | false new TestMqttUser(null, "username2", null) | new AllOfCondition(userNameEquals("username1")) | false diff --git a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy index c887bd04..41c09c13 100644 --- a/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy +++ b/acl-engine/src/test/groovy/javasabr/mqtt/acl/engine/model/rule/RuleTest.groovy @@ -26,7 +26,7 @@ class RuleTest extends UnitSpecification implements ConditionMatcherAware { SUBSCRIBE | new AllowPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false PUBLISH | new AllowSubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false SUBSCRIBE | new AllowSubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true - PUBLISH | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true + PUBLISH | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true SUBSCRIBE | new DenyPublishRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false PUBLISH | new DenySubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | false SUBSCRIBE | new DenySubscribeRule(clientIdEquals("clientId"), TopicCondition.MATCH_ANY) | true From 59dabc45f6b387957dd62a1331b6ba81554f6198 Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 16 Dec 2025 06:32:52 +0100 Subject: [PATCH 018/107] resolve code review comments --- .../acl/service/impl/GroovyDslBasedAuthorizationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java index 43061325..6d9a5a52 100644 --- a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.Map; import javasabr.mqtt.acl.engine.AclEngine; +import javasabr.mqtt.acl.engine.exception.AclConfigurationException; import javasabr.mqtt.acl.engine.model.rule.Rule; import javasabr.mqtt.acl.groovy.dsl.loader.AclRulesLoader; import javasabr.mqtt.acl.service.AclEngineBasedAuthorizationService; @@ -20,9 +21,9 @@ public class GroovyDslBasedAuthorizationService extends AclEngineBasedAuthorizat public void loadFrom(URI resource) { Path localFile = Path.of(resource); if (Files.notExists(localFile)) { - throw new IllegalArgumentException("ACL configuration:[%s] doesn't exist".formatted(resource)); + throw new AclConfigurationException("ACL configuration:[%s] doesn't exist".formatted(resource)); } else if (Files.isDirectory(localFile)) { - throw new IllegalArgumentException("ACL configuration:[%s] is directory".formatted(resource)); + throw new AclConfigurationException("ACL configuration:[%s] is directory".formatted(resource)); } Map> loadedAclRulesMap = AclRulesLoader.load(localFile); switchTo(new AclEngine(loadedAclRulesMap)); From f1bd443444e2466aa1f64f5520bc83b6a6c5f216 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:46:07 +0100 Subject: [PATCH 019/107] [broker-141] Introduce Flyway --- application/build.gradle | 15 +- .../config/AuthenticationSpringConfig.java | 96 -------- .../DatabaseCredentialReaderProperties.java | 6 + .../DatabaseCredentialWriterProperties.java | 6 + .../config/DatabaseSpringConfig.java | 74 ++++++ .../config/MqttBrokerSpringConfig.java | 11 +- .../ApplicationPropertiesSpecification.groovy | 75 ++++++ .../application/AuthenticationTest.groovy | 186 --------------- .../CredentialsSourceTestConfig.groovy | 36 --- .../application/ExternalConnectionTest.groovy | 52 +---- .../IntegrationSpecification.groovy | 3 +- .../config/CredentialsSourceTestConfig.groovy | 43 ++++ .../config/MqttBrokerTestConfig.groovy | 1 + .../service/AuthenticationServiceTest.groovy | 221 ++++++++++++++++++ .../resources/application-test.properties | 9 +- .../src/test/resources/auth/credentials-test | 4 +- .../resources/auth/user-credentials-data.sql | 1 - .../db/migration/V1.1__Insert_Test_User.sql | 2 + .../build.gradle | 2 +- .../PasswordBasedAuthenticationProvider.java | 6 +- .../build.gradle | 3 +- .../api/AnonymousAuthenticationProvider.java | 17 ++ .../javasabr/mqtt/auth/api}/AuthRequest.java | 2 +- .../auth/api}/AuthenticationProvider.java | 2 +- .../mqtt/auth/api}/AuthenticationService.java | 3 +- .../mqtt/auth/api}/CredentialSource.java | 2 +- .../auth/api}/CredentialsSourceException.java | 2 +- .../auth/api}/InMemoryCredentialSource.java | 6 +- .../auth/source/DatabaseProperties.java | 18 -- .../service/auth/source/package-info.java | 4 - authentication-service/build.gradle | 21 ++ .../DefaultAuthenticationService.java | 18 +- .../BasicAuthenticationSpringConfig.java | 83 +++++++ .../CredentialsSourceDatabaseProperties.java | 7 +- .../mqtt/auth/service}/package-info.java | 2 +- .../mqtt/service/auth/package-info.java | 4 - core-service/build.gradle | 2 +- .../ConnectToAuthRequestConverter.java | 2 +- .../impl/ConnectInMqttInMessageHandler.java | 4 +- credentials-source-db/build.gradle | 5 +- .../source/R2dbcCredentialsSource.java | 21 +- .../service/auth/source/package-info.java | 4 - .../V1__Create_User_Credentials_Table.sql | 3 +- credentials-source-file/build.gradle | 2 +- .../source/FileCredentialsSource.java | 7 +- .../service/auth/source/package-info.java | 4 - gradle/libs.versions.toml | 9 +- settings.gradle | 5 +- 48 files changed, 636 insertions(+), 475 deletions(-) delete mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy delete mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy delete mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy delete mode 100644 application/src/test/resources/auth/user-credentials-data.sql create mode 100644 application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql rename {authentication-basic => auth-provider-basic}/build.gradle (87%) rename {authentication-basic/src/main/java/javasabr/mqtt/service/auth => auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider}/PasswordBasedAuthenticationProvider.java (82%) rename {authentication => authentication-api}/build.gradle (73%) create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java rename {model/src/main/java/javasabr/mqtt/model/auth => authentication-api/src/main/java/javasabr/mqtt/auth/api}/AuthRequest.java (77%) rename {authentication/src/main/java/javasabr/mqtt/service/auth/provider => authentication-api/src/main/java/javasabr/mqtt/auth/api}/AuthenticationProvider.java (84%) rename {authentication/src/main/java/javasabr/mqtt/service/auth => authentication-api/src/main/java/javasabr/mqtt/auth/api}/AuthenticationService.java (62%) rename {authentication-basic/src/main/java/javasabr/mqtt/service/auth/source => authentication-api/src/main/java/javasabr/mqtt/auth/api}/CredentialSource.java (78%) rename {model/src/main/java/javasabr/mqtt/model/exception => authentication-api/src/main/java/javasabr/mqtt/auth/api}/CredentialsSourceException.java (85%) rename {authentication-basic/src/main/java/javasabr/mqtt/service/auth/source => authentication-api/src/main/java/javasabr/mqtt/auth/api}/InMemoryCredentialSource.java (87%) delete mode 100644 authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java delete mode 100644 authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java create mode 100644 authentication-service/build.gradle rename {authentication/src/main/java/javasabr/mqtt/service/auth => authentication-service/src/main/java/javasabr/mqtt/auth/service}/DefaultAuthenticationService.java (53%) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java rename {application/src/main/java/javasabr/mqtt/broker/application => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/CredentialsSourceDatabaseProperties.java (65%) rename {authentication-basic/src/main/java/javasabr/mqtt/service/auth => authentication-service/src/main/java/javasabr/mqtt/auth/service}/package-info.java (61%) delete mode 100644 authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java rename credentials-source-db/src/main/java/javasabr/mqtt/{service/auth => auth/credentials}/source/R2dbcCredentialsSource.java (58%) delete mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java rename application/src/test/resources/auth/user-credentials-schema.sql => credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql (50%) rename credentials-source-file/src/main/java/javasabr/mqtt/{service/auth => auth/credentials}/source/FileCredentialsSource.java (88%) delete mode 100644 credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java diff --git a/application/build.gradle b/application/build.gradle index ccdc4da0..7dd85a10 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -11,13 +11,24 @@ dependencies { implementation projects.coreService implementation projects.aclService implementation projects.aclGroovyDsl - implementation projects.credentialsSourceFile - implementation projects.credentialsSourceDb + implementation projects.authProviderBasic + implementation projects.authenticationApi + implementation projects.authenticationService implementation libs.rlib.logger.slf4j implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 + implementation libs.jdbc.postgres + implementation libs.flyway.core + implementation libs.r2dbc.spi + implementation libs.r2dbc.pool + implementation libs.r2dbc.postgresql + implementation libs.flyway.postgresql + testImplementation 'org.springframework.boot:spring-boot-test:3.2.0' + testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation libs.r2dbc.h2 + testImplementation projects.credentialsSourceFile + testImplementation projects.credentialsSourceDb testImplementation projects.testSupport testImplementation testFixtures(projects.network) } diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java deleted file mode 100644 index caa8bcab..00000000 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/AuthenticationSpringConfig.java +++ /dev/null @@ -1,96 +0,0 @@ -package javasabr.mqtt.broker.application.config; - -import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; -import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; -import java.util.List; -import java.util.Map; -import javasabr.mqtt.service.auth.AuthenticationService; -import javasabr.mqtt.service.auth.DefaultAuthenticationService; -import javasabr.mqtt.service.auth.PasswordBasedAuthenticationProvider; -import javasabr.mqtt.service.auth.provider.AuthenticationProvider; -import javasabr.mqtt.service.auth.source.CredentialSource; -import javasabr.mqtt.service.auth.source.DatabaseProperties; -import javasabr.mqtt.service.auth.source.FileCredentialsSource; -import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource; -import javasabr.rlib.collections.dictionary.DictionaryFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.r2dbc.core.DatabaseClient; - -@Configuration(proxyBeanMethods = false) -public class AuthenticationSpringConfig { - - private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; - private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; - - @Bean - ConnectionFactory connectionFactory(DatabaseProperties config) { - Map timeoutOptions = Map.of( - LOCK_TIMEOUT_OPTION, config.lockTimeout(), - STATEMENT_TIMEOUT_OPTION, config.statementTimeout()); - ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions - .builder() - .option(DRIVER, config.driver()) - .option(HOST, config.host()) - .option(PORT, config.port()) - .option(USER, config.username()) - .option(PASSWORD, config.password()) - .option(DATABASE, config.name()) - .option(OPTIONS, timeoutOptions) - .build(); - ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration - .builder(connectionFactory) - .maxIdleTime(config.maxIdleTime()) - .maxSize(config.maxPoolSize()) - .initialSize(config.initialPoolSize()) - .build(); - return new ConnectionPool(configuration); - } - - @Bean - DatabaseClient databaseClient(ConnectionFactory connectionFactory) { - return DatabaseClient.create(connectionFactory); - } - - @Bean - CredentialSource credentialSource(@Value("${credentials.source.file.name:credentials}") String fileName) { - return new FileCredentialsSource(fileName); - } - - @Bean - CredentialSource dbCredentialSource(DatabaseClient connectionFactory, DatabaseProperties databaseProperties) { - return new R2dbcCredentialsSource(connectionFactory, databaseProperties.credentialsQuery()); - } - - @Bean - AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { - return new PasswordBasedAuthenticationProvider(credentialSource); - } - - @Bean - AuthenticationService authenticationService( - List authenticationProviders, - @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth, - @Value("${authentication.provider.default:basic}") String defaultProviderName) { - var providers = DictionaryFactory.mutableRefToRefDictionary(String.class, AuthenticationProvider.class); - authenticationProviders.forEach(value -> providers.put(value.getAuthMethodName(), value)); - AuthenticationProvider defaultProvider = providers.get(defaultProviderName); - if (defaultProvider == null) { - throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); - } - return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider, allowAnonymousAuth); - } -} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java new file mode 100644 index 00000000..9307f1ab --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.broker.application.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "credentials.source.db.reader") +public record DatabaseCredentialReaderProperties(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java new file mode 100644 index 00000000..b1742785 --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.broker.application.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "credentials.source.db.writer") +public record DatabaseCredentialWriterProperties(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java new file mode 100644 index 00000000..46a34d44 --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -0,0 +1,74 @@ +package javasabr.mqtt.broker.application.config; + +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import java.util.Map; +import javasabr.mqtt.auth.service.config.CredentialsSourceDatabaseProperties; +import org.flywaydb.core.Flyway; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +@Configuration(proxyBeanMethods = false) +public class DatabaseSpringConfig { + + private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; + private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; + + @Bean + @DependsOn("flyway") + ConnectionFactory connectionFactory( + CredentialsSourceDatabaseProperties config, + DatabaseCredentialReaderProperties readerProperties) { + Map timeoutOptions = Map.of( + LOCK_TIMEOUT_OPTION, config.lockTimeout(), + STATEMENT_TIMEOUT_OPTION, config.statementTimeout()); + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() + .option(DRIVER, config.driver()) + .option(HOST, config.host()) + .option(PORT, config.port()) + .option(USER, readerProperties.username()) + .option(PASSWORD, readerProperties.password()) + .option(DATABASE, config.name()) + .option(OPTIONS, timeoutOptions).build(); + ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) + .maxIdleTime(config.maxIdleTime()) + .maxSize(config.maxPoolSize()) + .initialSize(config.initialPoolSize()) + .build(); + return new ConnectionPool(configuration); + } + + @Bean(initMethod = "migrate") + public Flyway flyway( + DatabaseUrlBuilder databaseUrlBuilder, + CredentialsSourceDatabaseProperties dbProperties, + DatabaseCredentialWriterProperties dbCredentials) { + return Flyway.configure() + .dataSource(databaseUrlBuilder.build(dbProperties), dbCredentials.username(), dbCredentials.password()) + .locations("db/migration") + .baselineOnMigrate(true) + .load(); + } + + @Bean + DatabaseUrlBuilder databaseUrlBuilder() { + return dbProps -> "jdbc:%s://%s:%s/%s".formatted(dbProps.driver(), dbProps.host(), dbProps.port(), dbProps.name()); + } + + public interface DatabaseUrlBuilder { + String build(CredentialsSourceDatabaseProperties dbProps); + } +} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 4ec9b04b..5312bf7c 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -4,6 +4,8 @@ import java.util.Collection; import java.util.List; import javasabr.mqtt.acl.service.conifg.GroovyDslBasedAclServiceSpringConfig; +import javasabr.mqtt.auth.api.AuthenticationService; +import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -21,7 +23,6 @@ import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; -import javasabr.mqtt.service.auth.AuthenticationService; import javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; @@ -81,7 +82,8 @@ @Import({ GroovyDslBasedAclServiceSpringConfig.class, - AuthenticationSpringConfig.class + DatabaseSpringConfig.class, + BasicAuthenticationSpringConfig.class }) @CustomLog @Configuration(proxyBeanMethods = false) @@ -90,7 +92,10 @@ @PropertySource(value = "file:./application.properties", ignoreResourceNotFound = true), @PropertySource(value = "${BROKER_CONFIG}", ignoreResourceNotFound = true) }) -@EnableConfigurationProperties(CredentialsSourceDatabaseProperties.class) +@EnableConfigurationProperties({ + DatabaseCredentialWriterProperties.class, + DatabaseCredentialReaderProperties.class +}) public class MqttBrokerSpringConfig { @Bean diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy new file mode 100644 index 00000000..bc83891c --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy @@ -0,0 +1,75 @@ +package javasabr.mqtt.broker.application + +import com.hivemq.client.mqtt.MqttClient +import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig +import org.springframework.boot.env.PropertiesPropertySourceLoader +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.core.io.ClassPathResource +import spock.lang.Specification + +class ApplicationPropertiesSpecification extends Specification { + + def loader = new PropertiesPropertySourceLoader() + def testProperties = loader.load("test-props", new ClassPathResource("application-test.properties")).get(0) + + def contextRunner = new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withInitializer { context -> + context.getEnvironment().getPropertySources().addLast(testProperties) + } + .withUserConfiguration(MqttBrokerTestConfig) + + void mqtt3ClientWithProperties(String[] properties, Closure assertion) { + contextRunner + .withPropertyValues(properties) + .run({ assertion(buildMqtt311Client(it.getBean(InetSocketAddress))) }) + } + + void mqtt5ClientWithProperties(String[] properties, Closure assertion) { + contextRunner + .withPropertyValues(properties) + .run({ assertion(buildMqtt5Client(it.getBean(InetSocketAddress))) }) + } + + def buildMqtt5Client(InetSocketAddress networkAddress) { + return buildMqtt5Client(generateClientId(), networkAddress) + } + + def buildMqtt5Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion5() + .addDisconnectedListener { + println "[${clientId}|mqtt5] disconnected:[${it.cause.message}]" + } + .build() + .toAsync() + } + + def buildMqtt311Client(InetSocketAddress networkAddress) { + return buildMqtt311Client(generateClientId(), networkAddress) + } + + def buildMqtt311Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion3() + .addDisconnectedListener { + println "[${clientId}|mqtt311] disconnected:[${it.cause.message}]" + } + .build() + .toAsync() + } + + def generateClientId() { + return generateClientId("Default") + } + + def generateClientId(String prefix) { + return prefix + "_" + IntegrationSpecification.idGenerator.incrementAndGet() + } +} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy deleted file mode 100644 index 1c679605..00000000 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/AuthenticationTest.groovy +++ /dev/null @@ -1,186 +0,0 @@ -package javasabr.mqtt.broker.application - -import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException -import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth -import com.hivemq.client.mqtt.mqtt3.message.connect.Mqtt3Connect -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException -import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth -import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestPropertySource - -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletionException - -@TestPropertySource(properties = ["authentication.allow.anonymous=false"]) -class AuthenticationTest { - - static class FileCredentialsSourceTest extends IntegrationSpecification { - - def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt3ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } - } - - def "should be able to connect with correct password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def correctPassword = "password" - and: - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() - } - - def "should not be able to connect with wrong password using mqtt 5 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt5ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } - } - - def "should be able to connect with correct password using mqtt 5 client"() { - given: - def existingUsername = "user" - def correctPassword = "password" - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() - } - } - - @ContextConfiguration(classes = CredentialsSourceTestConfig) - static class R2dbcCredentialsSourceTest extends IntegrationSpecification { - - def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt3ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } - } - - def "should be able to connect with correct password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - byte[] correctPassword = new byte[]{0x01, 0xBC, 0x2A} - and: - def subscriber = buildExternalMqtt311Client() - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() - } - - def "should not be able to connect with wrong password using mqtt 5 client"() { - given: - def existingUsername = "user" - def wrongPassword = "password1" - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword.getBytes(StandardCharsets.UTF_8)) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - def e = thrown(CompletionException.class) - with(e.cause as Mqtt5ConnAckException) { - message == "CONNECT failed as CONNACK contained an Error Code: BAD_USER_NAME_OR_PASSWORD." - } - } - - def "should be able to connect with correct password using mqtt 5 client"() { - given: - def existingUsername = "user" - byte[] correctPassword = new byte[]{0x01, 0xBC, 0x2A} - and: - def subscriber = buildExternalMqtt5Client() - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword) - .build()) - .build() - when: - subscriber.connect(connectMessage).join() - then: - noExceptionThrown() - cleanup: - subscriber.disconnect().join() - } - } -} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy deleted file mode 100644 index 8a6be36a..00000000 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/CredentialsSourceTestConfig.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package javasabr.mqtt.broker.application - -import io.r2dbc.spi.ConnectionFactories -import io.r2dbc.spi.ConnectionFactory -import javasabr.mqtt.broker.application.config.CredentialsSourceDatabaseProperties -import javasabr.mqtt.service.auth.source.CredentialSource -import javasabr.mqtt.service.auth.source.R2dbcCredentialsSource -import org.springframework.context.annotation.Bean -import org.springframework.core.io.ClassPathResource -import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer -import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator -import org.springframework.r2dbc.core.DatabaseClient - -class CredentialsSourceTestConfig { - - @Bean - CredentialSource credentialSource(DatabaseClient databaseClient, CredentialsSourceDatabaseProperties properties) { - return new R2dbcCredentialsSource(databaseClient, properties.credentialsQuery()) - } - - @Bean - ConnectionFactory connectionFactory() { - return ConnectionFactories.get("r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1") - } - - @Bean - ConnectionFactoryInitializer datasourceInitializer(ConnectionFactory connectionFactory) { - ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer() - initializer.setConnectionFactory(connectionFactory) - ResourceDatabasePopulator populator = new ResourceDatabasePopulator() - populator.addScript(new ClassPathResource("auth/user-credentials-schema.sql")) - populator.addScript(new ClassPathResource("auth/user-credentials-data.sql")) - initializer.setDatabasePopulator(populator) - return initializer - } -} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy index 47ad60af..bc7708e3 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy @@ -52,7 +52,7 @@ class ExternalConnectionTest extends IntegrationSpecification { given: def client = buildExternalMqtt311Client() when: - def result = connectWith(client, 'user1', 'password') + def result = connectWith(client, '', '') then: result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS !result.sessionPresent @@ -64,7 +64,7 @@ class ExternalConnectionTest extends IntegrationSpecification { given: def client = buildExternalMqtt5Client() when: - def result = connectWith(client, 'user1', 'password') + def result = connectWith(client, '', '') then: result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS result.sessionExpiryInterval.present @@ -128,52 +128,4 @@ class ExternalConnectionTest extends IntegrationSpecification { where: clientId << ["!@#!@*()^&"] } - - def "client should not connect to broker with wrong pass using mqtt 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD - } - - @Ignore - def "client should not connect to broker without username and with pass using mqtt 3.1.1"() { - given: - def client = buildMqtt311MockClient() - def clientId = generateClientId() - when: - - client.connect() - client.send(new ConnectMqtt311OutMessage( - "", - "", - clientId, - "wrongPassword".getBytes(StandardCharsets.UTF_8), - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - false, - false - )) - - def connectAck = client.readNext() as ConnectAckMqttInMessage - - then: - connectAck.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } - - def "client should not connect to broker with wrong pass using mqtt 5"() { - given: - def client = buildExternalMqtt5Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt5ConnAckException - cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy index ea339ce3..d50242a3 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy @@ -12,7 +12,6 @@ import javasabr.mqtt.network.MqttConnection import javasabr.mqtt.network.MqttMockClient import javasabr.mqtt.network.user.ConfigurableNetworkMqttUser import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import spock.lang.Specification @@ -31,7 +30,7 @@ class IntegrationSpecification extends Specification { public static final clientId = "testClientId" public static final keepAlive = 120 - private static final idGenerator = new AtomicInteger(1) + public static final idGenerator = new AtomicInteger(1) @Autowired InetSocketAddress externalNetworkAddress diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy new file mode 100644 index 00000000..2fd976e0 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy @@ -0,0 +1,43 @@ +package javasabr.mqtt.broker.application.config + +import io.r2dbc.spi.ConnectionFactories +import io.r2dbc.spi.ConnectionFactory +import io.r2dbc.spi.ConnectionFactoryOptions +import io.r2dbc.spi.Option +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.DependsOn +import org.springframework.context.annotation.Primary + +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD +import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL +import static io.r2dbc.spi.ConnectionFactoryOptions.USER +import static io.r2dbc.spi.ConnectionFactoryOptions.builder +import static DatabaseSpringConfig.DatabaseUrlBuilder + +@Configuration +class CredentialsSourceTestConfig { + + @Bean + @Primary + @DependsOn("flyway") + ConnectionFactory connectionFactory(DatabaseCredentialReaderProperties readerProperties) { + ConnectionFactoryOptions options = builder() + .option(DRIVER, "h2") + .option(PROTOCOL, "mem") + .option(DATABASE, "testdb") + .option(USER, readerProperties.username()) + .option(PASSWORD, readerProperties.password()) + .option(Option.valueOf("DB_CLOSE_DELAY"), "-1") + .build() + return ConnectionFactories.get(options) + } + + @Bean + @Primary + DatabaseUrlBuilder databaseUrlBuilder() { + return { dbProps -> "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" } + } +} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy index 2d5a18c6..cc61c748 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy @@ -13,6 +13,7 @@ import java.util.concurrent.ThreadLocalRandom @Import([ MqttBrokerSpringConfig, + CredentialsSourceTestConfig ]) @Configuration(proxyBeanMethods = false) class MqttBrokerTestConfig { diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy new file mode 100644 index 00000000..4c051634 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -0,0 +1,221 @@ +package javasabr.mqtt.broker.application.service + +import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient +import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException +import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth +import com.hivemq.client.mqtt.mqtt3.message.connect.Mqtt3Connect +import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAck +import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth +import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import javasabr.mqtt.broker.application.ApplicationPropertiesSpecification + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletionException + +class AuthenticationServiceTest extends ApplicationPropertiesSpecification { + + def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + def wrongPassword = "wrong-password".getBytes(StandardCharsets.UTF_8) + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + // when + try { + subscriber.connect(connectMessage).join() + throw new AssertionError("MQTT 3 client is able to connect with wrong password" as Object) + // then + } catch (CompletionException e) { + assert e.cause instanceof Mqtt3ConnAckException + def connAckEx = e.cause as Mqtt3ConnAckException + assert connAckEx.connAck.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + } + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } + + def "should not be able to connect with wrong password using mqtt 5 client"() { + given: + def existingUsername = "user" + def wrongPassword = "wrong-password".getBytes(StandardCharsets.UTF_8) + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(wrongPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + // when + try { + subscriber.connect(connectMessage).join() + throw new AssertionError("MQTT 5 client is able to connect with wrong password" as Object) + // then + } catch (CompletionException e) { + assert e.cause instanceof Mqtt5ConnAckException + def connAckEx = e.cause as Mqtt5ConnAckException + assert connAckEx.connAck.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } + + def "should be able to connect with correct password using mqtt 3.1.1 client"() { + given: + def existingUsername = "user" + byte[] correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) + and: + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + // when + Mqtt3ConnAck ack = subscriber.connect(connectMessage).join() + // then + assert ack instanceof Mqtt3ConnAck + assert ack.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + subscriber.disconnect().join() + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } + + def "should be able to connect with correct password using mqtt 5 client"() { + given: + def existingUsername = "user" + byte[] correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) + and: + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(existingUsername) + .password(correctPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + // when + Mqtt5ConnAck ack = subscriber.connect(connectMessage).join() + // then + assert ack instanceof Mqtt5ConnAck + assert ack.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + subscriber.disconnect().join() + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } + + def "should not be able to connect without username and with correct password using mqtt 5 client"() { + given: + def blankUsername = "" + def correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) + and: + def connectMessage = Mqtt5Connect.builder() + .simpleAuth(Mqtt5SimpleAuth.builder() + .username(blankUsername) + .password(correctPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + // when + try { + subscriber.connect(connectMessage).join() + throw new AssertionError("MQTT 5 client is able to connect with blank username" as Object) + // then + } catch (CompletionException e) { + assert e.cause instanceof Mqtt5ConnAckException + def connAckEx = e.cause as Mqtt5ConnAckException + assert connAckEx.connAck.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } + + def "should not be able to connect without username and with correct password using mqtt 3.1.1 client"() { + given: + def emptyUserName = "" + def correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) + and: + def connectMessage = Mqtt3Connect.builder() + .simpleAuth(Mqtt3SimpleAuth.builder() + .username(emptyUserName) + .password(correctPassword) + .build()) + .build() + String[] authenticationProperties = new String[]{ + "authentication.allow.anonymous=false", + "authentication.provider=$provider", + "authentication.credentials.source=$source" + } + expect: + mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + // when + try { + subscriber.connect(connectMessage).join() + throw new AssertionError("MQTT 5 client is able to connect with blank username" as Object) + // then + } catch (CompletionException e) { + assert e.cause instanceof Mqtt3ConnAckException + def connAckEx = e.cause as Mqtt3ConnAckException + assert connAckEx.connAck.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + } + } + where: + source | provider + "file" | "basic" + "database" | "basic" + } +} \ No newline at end of file diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 780e665e..c249e818 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -2,15 +2,16 @@ authentication.allow.anonymous=true credentials.source.file.name=auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true -credentials.source.db.username=user -credentials.source.db.password=pass -credentials.source.db.driver=postgres +credentials.source.db.reader.username=user +credentials.source.db.reader.password= +credentials.source.db.driver=h2 credentials.source.db.host=localhost credentials.source.db.port=5432 credentials.source.db.name=database-name -credentials.source.db.credentials-query=SELECT password FROM user_credentials WHERE username = $1 credentials.source.db.max-idle-time=30m credentials.source.db.initial-pool-size=5 credentials.source.db.max-pool-size=10 credentials.source.db.lock-timeout=10s credentials.source.db.statement-timeout=5m +credentials.source.db.writer.username=user +credentials.source.db.writer.password= diff --git a/application/src/test/resources/auth/credentials-test b/application/src/test/resources/auth/credentials-test index 9f363386..5f2567ab 100644 --- a/application/src/test/resources/auth/credentials-test +++ b/application/src/test/resources/auth/credentials-test @@ -1,2 +1,2 @@ -user=password -user1=password +user=correct-password +user1=correct-password diff --git a/application/src/test/resources/auth/user-credentials-data.sql b/application/src/test/resources/auth/user-credentials-data.sql deleted file mode 100644 index 997d61f3..00000000 --- a/application/src/test/resources/auth/user-credentials-data.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO user_credentials(username, password) VALUES ('user', X'01 bc 2a'); diff --git a/application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql b/application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql new file mode 100644 index 00000000..48da9df3 --- /dev/null +++ b/application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql @@ -0,0 +1,2 @@ +INSERT INTO user_credentials(username, password) +VALUES ('user', X'63 6f 72 72 65 63 74 2d 70 61 73 73 77 6f 72 64'); diff --git a/authentication-basic/build.gradle b/auth-provider-basic/build.gradle similarity index 87% rename from authentication-basic/build.gradle rename to auth-provider-basic/build.gradle index c92e1f2f..633cdf83 100644 --- a/authentication-basic/build.gradle +++ b/auth-provider-basic/build.gradle @@ -8,7 +8,7 @@ description = "Basic authentication service" dependencies { api projects.base - api projects.authentication + api projects.authenticationApi testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java similarity index 82% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java rename to auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java index 2db978e2..6dbb447f 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/PasswordBasedAuthenticationProvider.java +++ b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java @@ -1,7 +1,7 @@ -package javasabr.mqtt.service.auth; +package javasabr.mqtt.auth.provider; -import javasabr.mqtt.service.auth.provider.AuthenticationProvider; -import javasabr.mqtt.service.auth.source.CredentialSource; +import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.CredentialSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; diff --git a/authentication/build.gradle b/authentication-api/build.gradle similarity index 73% rename from authentication/build.gradle rename to authentication-api/build.gradle index 17b82b85..996b48bc 100644 --- a/authentication/build.gradle +++ b/authentication-api/build.gradle @@ -4,11 +4,10 @@ plugins { id("groovy") } -description = "Authentication service interface" +description = "Authentication API" dependencies { api projects.base - api projects.model testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java new file mode 100644 index 00000000..74f6659d --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.auth.api; + +import javasabr.rlib.common.util.StringUtils; +import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Mono; + +public class AnonymousAuthenticationProvider implements AuthenticationProvider { + @Override + public String getAuthMethodName() { + return "anonymous"; + } + + @Override + public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { + return Mono.just(StringUtils.isEmpty(username)); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java similarity index 77% rename from model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java index 989b53a1..b725f84e 100644 --- a/model/src/main/java/javasabr/mqtt/model/auth/AuthRequest.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.model.auth; +package javasabr.mqtt.auth.api; public record AuthRequest(String username, byte[] password, String authenticationMethod, byte[] authenticationData) {} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java similarity index 84% rename from authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index f0e85634..6832fef9 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/provider/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.auth.provider; +package javasabr.mqtt.auth.api; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java similarity index 62% rename from authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java index 8aa0a161..ef879f7f 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/AuthenticationService.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java @@ -1,6 +1,5 @@ -package javasabr.mqtt.service.auth; +package javasabr.mqtt.auth.api; -import javasabr.mqtt.model.auth.AuthRequest; import reactor.core.publisher.Mono; public interface AuthenticationService { diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java similarity index 78% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java index 29fc8bc8..f11c5251 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/CredentialSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.auth.source; +package javasabr.mqtt.auth.api; import reactor.core.publisher.Mono; diff --git a/model/src/main/java/javasabr/mqtt/model/exception/CredentialsSourceException.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java similarity index 85% rename from model/src/main/java/javasabr/mqtt/model/exception/CredentialsSourceException.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java index 6d03fb62..af7e1cce 100644 --- a/model/src/main/java/javasabr/mqtt/model/exception/CredentialsSourceException.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.exception; +package javasabr.mqtt.auth.api; public class CredentialsSourceException extends RuntimeException { diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java similarity index 87% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java index 1c9d4056..d728f981 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/InMemoryCredentialSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.service.auth.source; +package javasabr.mqtt.auth.api; import java.util.Arrays; import javasabr.rlib.collections.dictionary.DictionaryFactory; @@ -11,9 +11,9 @@ public abstract class InMemoryCredentialSource implements CredentialSource { private final LockableRefToRefDictionary credentials = DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); - abstract void init(); + protected abstract void init(); - void reset(RefToRefDictionary otherCredentials) { + protected void reset(RefToRefDictionary otherCredentials) { long stamp = credentials.writeLock(); try { credentials.clear(); diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java deleted file mode 100644 index db1a6bb4..00000000 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/DatabaseProperties.java +++ /dev/null @@ -1,18 +0,0 @@ -package javasabr.mqtt.service.auth.source; - -import java.time.Duration; - -public interface DatabaseProperties { - String username(); - String password(); - String driver(); - String host(); - int port(); - String name(); - String credentialsQuery(); - Duration maxIdleTime(); - int initialPoolSize(); - int maxPoolSize(); - String lockTimeout(); - String statementTimeout(); -} diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java deleted file mode 100644 index 67648f86..00000000 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/source/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package javasabr.mqtt.service.auth.source; - -import org.jspecify.annotations.NullMarked; diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle new file mode 100644 index 00000000..c64a7163 --- /dev/null +++ b/authentication-service/build.gradle @@ -0,0 +1,21 @@ +plugins { + id("java-library") + id("configure-java") + id("groovy") +} + +description = "Authentication service interface" + +dependencies { + api projects.base + api projects.model + api projects.authenticationApi + api libs.springboot.starter.autoconfigure + + compileOnlyApi projects.credentialsSourceDb + compileOnlyApi projects.credentialsSourceFile + compileOnlyApi projects.authProviderBasic + + testImplementation projects.testSupport + testFixturesApi projects.testSupport +} diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java similarity index 53% rename from authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 2a79d0a1..4388918b 100644 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,9 +1,9 @@ -package javasabr.mqtt.service.auth; +package javasabr.mqtt.auth.service; -import javasabr.mqtt.model.auth.AuthRequest; -import javasabr.mqtt.service.auth.provider.AuthenticationProvider; +import javasabr.mqtt.auth.api.AuthRequest; +import javasabr.mqtt.auth.api.AuthenticationService; +import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.rlib.collections.dictionary.RefToRefDictionary; -import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -15,17 +15,11 @@ public class DefaultAuthenticationService implements AuthenticationService { RefToRefDictionary providers; AuthenticationProvider defaultProvider; - boolean allowAnonymousAuth; @Override public Mono authenticate(AuthRequest request) { String username = request.username(); - if (allowAnonymousAuth && StringUtils.isEmpty(username)) { - return Mono.just(true); - } else { - return providers - .getOrDefault(request.authenticationMethod(), defaultProvider) - .authenticate(username, request.password(), request.authenticationData()); - } + return providers.getOrDefault(request.authenticationMethod(), defaultProvider) + .authenticate(username, request.password(), request.authenticationData()); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java new file mode 100644 index 00000000..9aec0ff3 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -0,0 +1,83 @@ +package javasabr.mqtt.auth.service.config; + +import io.r2dbc.spi.ConnectionFactory; +import java.util.List; +import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationService; +import javasabr.mqtt.auth.api.CredentialSource; +import javasabr.mqtt.auth.service.DefaultAuthenticationService; +import javasabr.mqtt.auth.credentials.source.R2dbcCredentialsSource; +import javasabr.mqtt.auth.provider.PasswordBasedAuthenticationProvider; +import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import lombok.CustomLog; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.r2dbc.core.DatabaseClient; + +@CustomLog +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties({ + CredentialsSourceDatabaseProperties.class +}) +public class BasicAuthenticationSpringConfig { + + @Bean + AuthenticationService authenticationService( + List authenticationProviders, + @Value("${authentication.provider.default:#{null}}") String defaultProviderName) { + log.info("Initializing Groovy-DSL based AuthorizationService..."); + if (authenticationProviders.isEmpty()) { + throw new IllegalArgumentException("Authenticator providers are not specified"); + } + var providers = DictionaryFactory.mutableRefToRefDictionary(String.class, AuthenticationProvider.class); + authenticationProviders.forEach(value -> providers.put(value.getAuthMethodName(), value)); + AuthenticationProvider defaultProvider; + if (defaultProviderName == null) { + defaultProvider = authenticationProviders.getFirst(); + } else { + defaultProvider = providers.get(defaultProviderName); + } + if (defaultProvider == null) { + throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); + } + return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider); + } + + @Bean + DatabaseClient databaseClient(ConnectionFactory connectionFactory) { + return DatabaseClient.create(connectionFactory); + } + + @Bean + @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "file") + @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") + CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") String fileName) { + return new FileCredentialsSource(fileName); + } + + @Bean + @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "database") + @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.R2dbcCredentialsSource") + CredentialSource dbCredentialSource(DatabaseClient connectionFactory, CredentialsSourceDatabaseProperties databaseProperties) { + return new R2dbcCredentialsSource(connectionFactory); + } + + @Bean + @ConditionalOnProperty(name = "authentication.provider", havingValue = "basic") + @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.PasswordBasedAuthenticationProvider") + AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { + return new PasswordBasedAuthenticationProvider(credentialSource); + } + + @Bean + @ConditionalOnProperty(name = "authentication.allow.anonymous", havingValue = "true") + AuthenticationProvider anonymousAuthenticationProvider() { + return new AnonymousAuthenticationProvider(); + } +} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java similarity index 65% rename from application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java index 6856579b..ce9a416c 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/CredentialsSourceDatabaseProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java @@ -1,13 +1,10 @@ -package javasabr.mqtt.broker.application.config; +package javasabr.mqtt.auth.service.config; import java.time.Duration; -import javasabr.mqtt.service.auth.source.DatabaseProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") public record CredentialsSourceDatabaseProperties( - String username, - String password, String driver, String host, int port, @@ -17,4 +14,4 @@ public record CredentialsSourceDatabaseProperties( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) implements DatabaseProperties {} + String statementTimeout) {} diff --git a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/package-info.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/package-info.java similarity index 61% rename from authentication-basic/src/main/java/javasabr/mqtt/service/auth/package-info.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/package-info.java index f70a6243..ac790154 100644 --- a/authentication-basic/src/main/java/javasabr/mqtt/service/auth/package-info.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.service.auth; +package javasabr.mqtt.auth.service; import org.jspecify.annotations.NullMarked; diff --git a/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java b/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java deleted file mode 100644 index f70a6243..00000000 --- a/authentication/src/main/java/javasabr/mqtt/service/auth/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package javasabr.mqtt.service.auth; - -import org.jspecify.annotations.NullMarked; diff --git a/core-service/build.gradle b/core-service/build.gradle index 8f5f1f40..661c0877 100644 --- a/core-service/build.gradle +++ b/core-service/build.gradle @@ -9,7 +9,7 @@ description = "Provides interfaces and minimal implementation of all required se dependencies { api projects.network api projects.aclEngine - api projects.authenticationBasic + api projects.authenticationApi testImplementation projects.testSupport testImplementation testFixtures(projects.network) diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java b/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java index 7ee8408e..9d43c22f 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java @@ -1,6 +1,6 @@ package javasabr.mqtt.service.message.converter; -import javasabr.mqtt.model.auth.AuthRequest; +import javasabr.mqtt.auth.api.AuthRequest; import javasabr.mqtt.network.message.in.ConnectMqttInMessage; public class ConnectToAuthRequestConverter implements MessageConverter { diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 968b874b..2a5ccff8 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,11 +11,12 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; +import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; -import javasabr.mqtt.model.auth.AuthRequest; +import javasabr.mqtt.auth.api.AuthRequest; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -27,7 +28,6 @@ import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; -import javasabr.mqtt.service.auth.AuthenticationService; import javasabr.mqtt.service.message.converter.ConnectToAuthRequestConverter; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.common.util.StringUtils; diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle index ca3c76fa..73b35cfd 100644 --- a/credentials-source-db/build.gradle +++ b/credentials-source-db/build.gradle @@ -8,10 +8,7 @@ description = "Database-based Credentials Source Provider" dependencies { api projects.base - api projects.authenticationBasic - api libs.r2dbc.spi - api libs.r2dbc.pool - api libs.r2dbc.postgresql + api projects.authenticationApi api libs.spring.r2dbc testImplementation projects.testSupport diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java similarity index 58% rename from credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java index ee09847f..09732e37 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/R2dbcCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java @@ -1,6 +1,7 @@ -package javasabr.mqtt.service.auth.source; +package javasabr.mqtt.auth.credentials.source; import java.util.Arrays; +import javasabr.mqtt.auth.api.CredentialSource; import lombok.RequiredArgsConstructor; import org.springframework.r2dbc.core.DatabaseClient; import reactor.core.publisher.Mono; @@ -8,24 +9,26 @@ @RequiredArgsConstructor public class R2dbcCredentialsSource implements CredentialSource { - private static final String CREDENTIALS_SOURCE_NAME = "database"; - private static final String PASSWORD_COLUMN = "password"; - private static final String USERNAME_BIND_PARAM = "$1"; + @SuppressWarnings("SqlNoDataSourceInspection") + private static final String CREDENTIALS_QUERY = """ + SELECT password + FROM user_credentials + WHERE username = $1 + """; private final DatabaseClient databaseClient; - private final String credentialsQuery; @Override public String getName() { - return CREDENTIALS_SOURCE_NAME; + return "database"; } @Override public Mono isCredentialExists(String username, byte[] requestedPassword) { return databaseClient - .sql(credentialsQuery) - .bind(USERNAME_BIND_PARAM, username) - .map(row -> row.get(PASSWORD_COLUMN, byte[].class)) + .sql(CREDENTIALS_QUERY) + .bind("$1", username) + .map(row -> row.get("password", byte[].class)) .all() .singleOrEmpty() .map(existingPassword -> Arrays.equals(existingPassword, requestedPassword)) diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java deleted file mode 100644 index 67648f86..00000000 --- a/credentials-source-db/src/main/java/javasabr/mqtt/service/auth/source/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package javasabr.mqtt.service.auth.source; - -import org.jspecify.annotations.NullMarked; diff --git a/application/src/test/resources/auth/user-credentials-schema.sql b/credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql similarity index 50% rename from application/src/test/resources/auth/user-credentials-schema.sql rename to credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql index 90da78e3..cb8b7aec 100644 --- a/application/src/test/resources/auth/user-credentials-schema.sql +++ b/credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql @@ -1,5 +1,4 @@ -DROP TABLE IF EXISTS user_credentials; CREATE TABLE user_credentials ( username VARCHAR(255), - password BINARY(3) + password VARBINARY(20) ); diff --git a/credentials-source-file/build.gradle b/credentials-source-file/build.gradle index 8be74f95..7adca82d 100644 --- a/credentials-source-file/build.gradle +++ b/credentials-source-file/build.gradle @@ -8,7 +8,7 @@ description = "File-based Credentials Source Provider" dependencies { api projects.base - api projects.authenticationBasic + api projects.authenticationApi testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java similarity index 88% rename from credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java rename to credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index e8b71d07..47f9968a 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -1,11 +1,12 @@ -package javasabr.mqtt.service.auth.source; +package javasabr.mqtt.auth.credentials.source; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Properties; -import javasabr.mqtt.model.exception.CredentialsSourceException; +import javasabr.mqtt.auth.api.CredentialsSourceException; +import javasabr.mqtt.auth.api.InMemoryCredentialSource; import javasabr.rlib.collections.dictionary.DictionaryCollectors; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; @@ -23,7 +24,7 @@ public FileCredentialsSource(String fileName) { } @Override - void init() { + protected void init() { URL credentialUrl = FileCredentialsSource.class .getClassLoader() .getResource(fileName); diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java b/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java deleted file mode 100644 index 67648f86..00000000 --- a/credentials-source-file/src/main/java/javasabr/mqtt/service/auth/source/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package javasabr.mqtt.service.auth.source; - -import org.jspecify.annotations.NullMarked; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 342f92c2..c48e5a82 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,10 @@ r2dbc = "1.0.0.RELEASE" r2dbc-pool="1.0.2.RELEASE" # https://mvnrepository.com/artifact/org.postgresql/r2dbc-postgresql r2dbc-postgresql="1.1.1.RELEASE" +# https://mvnrepository.com/artifact/org.postgresql/postgresql +jdbc-postgres="42.7.8" +# https://mvnrepository.com/artifact/org.flywaydb/flyway-core +flyway="11.19.0" # https://mvnrepository.com/artifact/org.spockframework/spock-core spock = "2.4-M6-groovy-4.0" # https://mvnrepository.com/artifact/org.apache.groovy/groovy-all @@ -54,7 +58,10 @@ project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "r2dbc" } r2dbc-spi = { module ='io.r2dbc:r2dbc-spi', version.ref = "r2dbc"} r2dbc-pool = { module = 'io.r2dbc:r2dbc-pool', version.ref = "r2dbc-pool"} -r2dbc-postgresql= { module = 'org.postgresql:r2dbc-postgresql', version.ref = "r2dbc-postgresql"} +r2dbc-postgresql = { module = 'org.postgresql:r2dbc-postgresql', version.ref = "r2dbc-postgresql"} +jdbc-postgres = { module = 'org.postgresql:postgresql', version.ref = "jdbc-postgres"} +flyway-core = { module = 'org.flywaydb:flyway-core', version.ref = "flyway"} +flyway-postgresql = { module = 'org.flywaydb:flyway-database-postgresql', version.ref = "flyway"} jackson-core = { module = "tools.jackson.core:jackson-core", version.ref = "jackson" } jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" } diff --git a/settings.gradle b/settings.gradle index 4ad6e190..332380d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,8 @@ include(":test-coverage") include(":acl-groovy-dsl") include(":acl-engine") include(":acl-service") -include(":authentication") -include(":authentication-basic") +include(":authentication-api") +include(":authentication-service") +include(":auth-provider-basic") include(":credentials-source-file") include(":credentials-source-db") From 1bb295680bc77448bd7d39102c38c438ad9d532c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:59:49 +0100 Subject: [PATCH 020/107] [broker-141] Improve initialization of authentication service --- .../resources/application-test.properties | 2 +- ....java => BasicAuthenticationProvider.java} | 10 ++-- .../api/AnonymousAuthenticationProvider.java | 2 +- .../api/AuthenticationConfigException.java | 8 +++ .../mqtt/auth/api/AuthenticationProvider.java | 2 +- .../auth/api/CredentialsSourceException.java | 4 +- .../auth/api/InMemoryCredentialSource.java | 18 ++++++- .../service/DefaultAuthenticationService.java | 38 ++++++++++++-- .../BasicAuthenticationSpringConfig.java | 33 +++++++----- ...ce.java => DatabaseCredentialsSource.java} | 13 ++++- .../source/FileCredentialsSource.java | 51 +++++++------------ 11 files changed, 119 insertions(+), 62 deletions(-) rename auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/{PasswordBasedAuthenticationProvider.java => BasicAuthenticationProvider.java} (75%) create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java rename credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/{R2dbcCredentialsSource.java => DatabaseCredentialsSource.java} (68%) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index c249e818..0f4e48de 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ authentication.allow.anonymous=true -credentials.source.file.name=auth/credentials-test +credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true credentials.source.db.reader.username=user diff --git a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java similarity index 75% rename from auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java rename to auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 6dbb447f..e51f3687 100644 --- a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/PasswordBasedAuthenticationProvider.java +++ b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -10,22 +10,26 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class PasswordBasedAuthenticationProvider implements AuthenticationProvider { +public class BasicAuthenticationProvider implements AuthenticationProvider { CredentialSource credentialsSource; @Override - public String getAuthMethodName() { + public String getName() { return "basic"; } @Override public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { - // processData(data); if (username == null) { return Mono.just(false); } else { return credentialsSource.isCredentialExists(username, password); } } + + @Override + public String toString() { + return "BasicAuthenticationProvider{credentialsSource=[%s]}".formatted(credentialsSource); + } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java index 74f6659d..2acf5c9f 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java @@ -6,7 +6,7 @@ public class AnonymousAuthenticationProvider implements AuthenticationProvider { @Override - public String getAuthMethodName() { + public String getName() { return "anonymous"; } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java new file mode 100644 index 00000000..8eec5042 --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java @@ -0,0 +1,8 @@ +package javasabr.mqtt.auth.api; + +public class AuthenticationConfigException extends RuntimeException { + + public AuthenticationConfigException(String message) { + super(message); + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index 6832fef9..d87447a9 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -5,7 +5,7 @@ public interface AuthenticationProvider { - String getAuthMethodName(); + String getName(); Mono authenticate(@Nullable String username, byte[] password, byte[] data); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java index af7e1cce..3e93d494 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java @@ -6,7 +6,7 @@ public CredentialsSourceException(String message) { super(message); } - public CredentialsSourceException(Throwable cause) { - super(cause); + public CredentialsSourceException(String message, Throwable cause) { + super(message, cause); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java index d728f981..3f60ad38 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java @@ -1,6 +1,11 @@ package javasabr.mqtt.auth.api; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Properties; +import javasabr.rlib.collections.dictionary.DictionaryCollectors; import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; import javasabr.rlib.collections.dictionary.RefToRefDictionary; @@ -11,8 +16,6 @@ public abstract class InMemoryCredentialSource implements CredentialSource { private final LockableRefToRefDictionary credentials = DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); - protected abstract void init(); - protected void reset(RefToRefDictionary otherCredentials) { long stamp = credentials.writeLock(); try { @@ -32,6 +35,17 @@ void put(String user, byte[] pass) { } } + protected void reset(InputStream inStream) throws IOException { + var credentialsProperties = new Properties(); + credentialsProperties.load(inStream); + + var credentials = credentialsProperties.entrySet().stream() + .collect(DictionaryCollectors.toRefToRefDictionary( + entry -> entry.getKey().toString(), + entry -> entry.getValue().toString().getBytes(StandardCharsets.UTF_8))); + reset(credentials); + } + @Override public Mono isCredentialExists(String user, byte[] pass) { return Mono.just(Arrays.equals(pass, credentials.get(user))); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 4388918b..d29f375a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,25 +1,57 @@ package javasabr.mqtt.auth.service; import javasabr.mqtt.auth.api.AuthRequest; -import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; +import lombok.CustomLog; import lombok.experimental.FieldDefaults; import reactor.core.publisher.Mono; -@RequiredArgsConstructor +@CustomLog @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { RefToRefDictionary providers; AuthenticationProvider defaultProvider; + public DefaultAuthenticationService( + RefToRefDictionary providers, + AuthenticationProvider defaultProvider) { + this.providers = providers; + this.defaultProvider = defaultProvider; + log.info(providers, defaultProvider, DefaultAuthenticationService::buildServiceDescription); + } + @Override public Mono authenticate(AuthRequest request) { String username = request.username(); return providers.getOrDefault(request.authenticationMethod(), defaultProvider) .authenticate(username, request.password(), request.authenticationData()); } + + private static String buildServiceDescription( + RefToRefDictionary providers, + AuthenticationProvider defaultProvider) { + + var builder = new StringBuilder(); + builder.append("{\n"); + builder.append(" \"DEFAULT\":\"").append(defaultProvider).append("\",\n"); + + for (AuthenticationProvider provider : providers) { + builder + .append(" \"") + .append(provider.getName()) + .append("\": ") + .append(provider) + .append(",") + .append("\n"); + } + builder + .delete(builder.length() - 2, builder.length()) + .append("\n}"); + + return "Loaded total [%s] authentication providers: %s".formatted(providers.size(), builder); + } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index 9aec0ff3..6e635d28 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -1,14 +1,17 @@ package javasabr.mqtt.auth.service.config; import io.r2dbc.spi.ConnectionFactory; +import java.io.IOException; +import java.net.URI; import java.util.List; import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationConfigException; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialSource; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.auth.credentials.source.R2dbcCredentialsSource; -import javasabr.mqtt.auth.provider.PasswordBasedAuthenticationProvider; +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; +import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; @@ -18,6 +21,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; import org.springframework.r2dbc.core.DatabaseClient; @CustomLog @@ -31,12 +35,12 @@ public class BasicAuthenticationSpringConfig { AuthenticationService authenticationService( List authenticationProviders, @Value("${authentication.provider.default:#{null}}") String defaultProviderName) { - log.info("Initializing Groovy-DSL based AuthorizationService..."); + log.info("Initializing AuthenticationService..."); if (authenticationProviders.isEmpty()) { - throw new IllegalArgumentException("Authenticator providers are not specified"); + throw new AuthenticationConfigException("Authenticator providers are not configured"); } var providers = DictionaryFactory.mutableRefToRefDictionary(String.class, AuthenticationProvider.class); - authenticationProviders.forEach(value -> providers.put(value.getAuthMethodName(), value)); + authenticationProviders.forEach(value -> providers.put(value.getName(), value)); AuthenticationProvider defaultProvider; if (defaultProviderName == null) { defaultProvider = authenticationProviders.getFirst(); @@ -44,7 +48,7 @@ AuthenticationService authenticationService( defaultProvider = providers.get(defaultProviderName); } if (defaultProvider == null) { - throw new IllegalArgumentException("[%s] authenticator provider not found".formatted(defaultProviderName)); + throw new AuthenticationConfigException("[%s] authenticator provider not found".formatted(defaultProviderName)); } return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider); } @@ -57,22 +61,25 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "file") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") String fileName) { - return new FileCredentialsSource(fileName); + CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") Resource fileName) + throws IOException { + FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName.getURI()); + fileCredentialsSource.init(); + return fileCredentialsSource; } @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "database") - @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.R2dbcCredentialsSource") - CredentialSource dbCredentialSource(DatabaseClient connectionFactory, CredentialsSourceDatabaseProperties databaseProperties) { - return new R2dbcCredentialsSource(connectionFactory); + @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") + CredentialSource dbCredentialSource(DatabaseClient connectionFactory) { + return new DatabaseCredentialsSource(connectionFactory); } @Bean @ConditionalOnProperty(name = "authentication.provider", havingValue = "basic") - @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.PasswordBasedAuthenticationProvider") + @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { - return new PasswordBasedAuthenticationProvider(credentialSource); + return new BasicAuthenticationProvider(credentialSource); } @Bean diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java similarity index 68% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 09732e37..f6c7a2a7 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/R2dbcCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -2,12 +2,15 @@ import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialSource; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; import org.springframework.r2dbc.core.DatabaseClient; import reactor.core.publisher.Mono; @RequiredArgsConstructor -public class R2dbcCredentialsSource implements CredentialSource { +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DatabaseCredentialsSource implements CredentialSource { @SuppressWarnings("SqlNoDataSourceInspection") private static final String CREDENTIALS_QUERY = """ @@ -16,7 +19,7 @@ public class R2dbcCredentialsSource implements CredentialSource { WHERE username = $1 """; - private final DatabaseClient databaseClient; + DatabaseClient databaseClient; @Override public String getName() { @@ -34,4 +37,10 @@ public Mono isCredentialExists(String username, byte[] requestedPasswor .map(existingPassword -> Arrays.equals(existingPassword, requestedPassword)) .defaultIfEmpty(false); } + + @Override + public String toString() { + String driver = databaseClient.getConnectionFactory().getMetadata().getName(); + return "DatabaseCredentialsSource{driver='%s'}".formatted(driver); + } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 47f9968a..5089554b 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -1,57 +1,40 @@ package javasabr.mqtt.auth.credentials.source; -import java.io.FileInputStream; import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Properties; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; import javasabr.mqtt.auth.api.CredentialsSourceException; import javasabr.mqtt.auth.api.InMemoryCredentialSource; -import javasabr.rlib.collections.dictionary.DictionaryCollectors; import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class FileCredentialsSource extends InMemoryCredentialSource { - private static final String CREDENTIALS_SOURCE_NAME = "file"; + URI fileName; - String fileName; - - public FileCredentialsSource(String fileName) { - this.fileName = fileName; - init(); - } - - @Override - protected void init() { - URL credentialUrl = FileCredentialsSource.class - .getClassLoader() - .getResource(fileName); - - if (credentialUrl == null) { + public void init() { + Path path = Path.of(fileName); + if (!Files.exists(path)) { throw new CredentialsSourceException("Credentials file:[%s] could not be found".formatted(fileName)); } - try { - var credentialsProperties = new Properties(); - credentialsProperties.load(new FileInputStream(credentialUrl.getPath())); - - var credentials = credentialsProperties - .entrySet() - .stream() - .collect(DictionaryCollectors.toRefToRefDictionary( - entry -> entry.getKey().toString(), - entry -> entry.getValue().toString().getBytes(StandardCharsets.UTF_8))); - - reset(credentials); + reset(Files.newInputStream(path)); } catch (IOException e) { - throw new CredentialsSourceException(e); + throw new CredentialsSourceException("Error during credentials file read", e); } } @Override public String getName() { - return CREDENTIALS_SOURCE_NAME; + return "file"; + } + + @Override + public String toString() { + return "FileCredentialsSource{fileName='%s'}".formatted(fileName); } } From d1e4e7fb71f11cf5613c0c7adae565a4217f46d8 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:53:38 +0100 Subject: [PATCH 021/107] [broker-141] Add logging during initialization of authentication service --- .../config/DatabaseSpringConfig.java | 42 ++++++++++--------- .../config/MqttBrokerSpringConfig.java | 5 --- .../db/DatabaseConnectionProperties.java | 8 ++-- .../credentials/DatabaseAdminCredential.java | 6 +++ .../DatabaseReaderCredential.java} | 4 +- .../DatabaseWriterCredential.java} | 4 +- .../config/CredentialsSourceTestConfig.groovy | 12 +++--- .../resources/application-test.properties | 4 +- .../provider/BasicAuthenticationProvider.java | 2 +- .../service/DefaultAuthenticationService.java | 6 +-- .../BasicAuthenticationSpringConfig.java | 24 +++++------ .../source/DatabaseCredentialsSource.java | 6 ++- .../source/FileCredentialsSource.java | 2 +- .../mqtt/model/DatabaseProperties.java | 26 ++++++++++++ .../mqtt/model/DatabaseUrlBuilder.java | 5 +++ 15 files changed, 93 insertions(+), 63 deletions(-) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java => application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java (50%) create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java rename application/src/main/java/javasabr/mqtt/broker/application/config/{DatabaseCredentialReaderProperties.java => db/credentials/DatabaseReaderCredential.java} (50%) rename application/src/main/java/javasabr/mqtt/broker/application/config/{DatabaseCredentialWriterProperties.java => db/credentials/DatabaseWriterCredential.java} (50%) create mode 100644 model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java create mode 100644 model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java index 46a34d44..95f131e7 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -14,32 +14,40 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.service.config.CredentialsSourceDatabaseProperties; +import javasabr.mqtt.broker.application.config.db.DatabaseConnectionProperties; +import javasabr.mqtt.broker.application.config.db.credentials.DatabaseAdminCredential; +import javasabr.mqtt.broker.application.config.db.credentials.DatabaseReaderCredential; +import javasabr.mqtt.broker.application.config.db.credentials.DatabaseWriterCredential; +import javasabr.mqtt.model.DatabaseUrlBuilder; import org.flywaydb.core.Flyway; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties({ + DatabaseConnectionProperties.class, + DatabaseAdminCredential.class, + DatabaseReaderCredential.class, + DatabaseWriterCredential.class +}) public class DatabaseSpringConfig { - private static final String LOCK_TIMEOUT_OPTION = "lock_timeout"; - private static final String STATEMENT_TIMEOUT_OPTION = "statement_timeout"; - @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - CredentialsSourceDatabaseProperties config, - DatabaseCredentialReaderProperties readerProperties) { + DatabaseConnectionProperties config, + DatabaseWriterCredential writerCredential) { Map timeoutOptions = Map.of( - LOCK_TIMEOUT_OPTION, config.lockTimeout(), - STATEMENT_TIMEOUT_OPTION, config.statementTimeout()); + "lock_timeout", config.lockTimeout(), + "statement_timeout", config.statementTimeout()); ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() .option(DRIVER, config.driver()) .option(HOST, config.host()) .option(PORT, config.port()) - .option(USER, readerProperties.username()) - .option(PASSWORD, readerProperties.password()) + .option(USER, writerCredential.username()) + .option(PASSWORD, writerCredential.password()) .option(DATABASE, config.name()) .option(OPTIONS, timeoutOptions).build(); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); @@ -52,14 +60,12 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - public Flyway flyway( + Flyway flyway( DatabaseUrlBuilder databaseUrlBuilder, - CredentialsSourceDatabaseProperties dbProperties, - DatabaseCredentialWriterProperties dbCredentials) { + DatabaseConnectionProperties dbProperties, + DatabaseAdminCredential adminCredential) { return Flyway.configure() - .dataSource(databaseUrlBuilder.build(dbProperties), dbCredentials.username(), dbCredentials.password()) - .locations("db/migration") - .baselineOnMigrate(true) + .dataSource(databaseUrlBuilder.build(dbProperties), adminCredential.username(), adminCredential.password()) .load(); } @@ -67,8 +73,4 @@ public Flyway flyway( DatabaseUrlBuilder databaseUrlBuilder() { return dbProps -> "jdbc:%s://%s:%s/%s".formatted(dbProps.driver(), dbProps.host(), dbProps.port(), dbProps.name()); } - - public interface DatabaseUrlBuilder { - String build(CredentialsSourceDatabaseProperties dbProps); - } } diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 5312bf7c..78ebb0ff 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -71,7 +71,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationStartedEvent; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -92,10 +91,6 @@ @PropertySource(value = "file:./application.properties", ignoreResourceNotFound = true), @PropertySource(value = "${BROKER_CONFIG}", ignoreResourceNotFound = true) }) -@EnableConfigurationProperties({ - DatabaseCredentialWriterProperties.class, - DatabaseCredentialReaderProperties.class -}) public class MqttBrokerSpringConfig { @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java similarity index 50% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java index ce9a416c..e7d5f7bb 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/CredentialsSourceDatabaseProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java @@ -1,10 +1,10 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.broker.application.config.db; import java.time.Duration; -import org.springframework.boot.context.properties.ConfigurationProperties; +import javasabr.mqtt.model.DatabaseProperties;import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") -public record CredentialsSourceDatabaseProperties( +public record DatabaseConnectionProperties( String driver, String host, int port, @@ -14,4 +14,4 @@ public record CredentialsSourceDatabaseProperties( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) {} + String statementTimeout) implements DatabaseProperties {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java new file mode 100644 index 00000000..4c80766b --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.broker.application.config.db.credentials; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "credentials.source.db.admin") +public record DatabaseAdminCredential(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java similarity index 50% rename from application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java index 9307f1ab..046894f5 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialReaderProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java @@ -1,6 +1,6 @@ -package javasabr.mqtt.broker.application.config; +package javasabr.mqtt.broker.application.config.db.credentials; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db.reader") -public record DatabaseCredentialReaderProperties(String username, String password) {} +public record DatabaseReaderCredential(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java similarity index 50% rename from application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java index b1742785..57aa2db7 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseCredentialWriterProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java @@ -1,6 +1,6 @@ -package javasabr.mqtt.broker.application.config; +package javasabr.mqtt.broker.application.config.db.credentials; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db.writer") -public record DatabaseCredentialWriterProperties(String username, String password) {} +public record DatabaseWriterCredential(String username, String password) {} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy index 2fd976e0..7bdd49d1 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy @@ -4,10 +4,11 @@ import io.r2dbc.spi.ConnectionFactories import io.r2dbc.spi.ConnectionFactory import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option +import javasabr.mqtt.broker.application.config.db.credentials.DatabaseWriterCredential +import javasabr.mqtt.model.DatabaseUrlBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.DependsOn -import org.springframework.context.annotation.Primary import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER @@ -15,28 +16,25 @@ import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL import static io.r2dbc.spi.ConnectionFactoryOptions.USER import static io.r2dbc.spi.ConnectionFactoryOptions.builder -import static DatabaseSpringConfig.DatabaseUrlBuilder @Configuration class CredentialsSourceTestConfig { @Bean - @Primary @DependsOn("flyway") - ConnectionFactory connectionFactory(DatabaseCredentialReaderProperties readerProperties) { + ConnectionFactory connectionFactory(DatabaseWriterCredential writerCredential) { ConnectionFactoryOptions options = builder() .option(DRIVER, "h2") .option(PROTOCOL, "mem") .option(DATABASE, "testdb") - .option(USER, readerProperties.username()) - .option(PASSWORD, readerProperties.password()) + .option(USER, writerCredential.username()) + .option(PASSWORD, writerCredential.password()) .option(Option.valueOf("DB_CLOSE_DELAY"), "-1") .build() return ConnectionFactories.get(options) } @Bean - @Primary DatabaseUrlBuilder databaseUrlBuilder() { return { dbProps -> "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" } } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 0f4e48de..7fb20ef7 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -2,8 +2,6 @@ authentication.allow.anonymous=true credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true -credentials.source.db.reader.username=user -credentials.source.db.reader.password= credentials.source.db.driver=h2 credentials.source.db.host=localhost credentials.source.db.port=5432 @@ -13,5 +11,7 @@ credentials.source.db.initial-pool-size=5 credentials.source.db.max-pool-size=10 credentials.source.db.lock-timeout=10s credentials.source.db.statement-timeout=5m +credentials.source.db.admin.username=user +credentials.source.db.admin.password= credentials.source.db.writer.username=user credentials.source.db.writer.password= diff --git a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index e51f3687..e456ecb4 100644 --- a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -30,6 +30,6 @@ public Mono authenticate(@Nullable String username, byte[] password, by @Override public String toString() { - return "BasicAuthenticationProvider{credentialsSource=[%s]}".formatted(credentialsSource); + return "\"%s\": { \"credentialSource\": %s }".formatted(getName(), credentialsSource); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index d29f375a..f65203e1 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -37,13 +37,11 @@ private static String buildServiceDescription( var builder = new StringBuilder(); builder.append("{\n"); - builder.append(" \"DEFAULT\":\"").append(defaultProvider).append("\",\n"); + builder.append(" \"DEFAULT\": \"").append(defaultProvider.getName()).append("\",\n"); for (AuthenticationProvider provider : providers) { builder - .append(" \"") - .append(provider.getName()) - .append("\": ") + .append(" ") .append(provider) .append(",") .append("\n"); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index 6e635d28..6fa25476 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -1,7 +1,6 @@ package javasabr.mqtt.auth.service.config; import io.r2dbc.spi.ConnectionFactory; -import java.io.IOException; import java.net.URI; import java.util.List; import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; @@ -9,26 +8,23 @@ import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialSource; -import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; -import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; +import javasabr.mqtt.auth.service.DefaultAuthenticationService; +import javasabr.mqtt.model.DatabaseProperties; +import javasabr.mqtt.model.DatabaseUrlBuilder; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; import org.springframework.r2dbc.core.DatabaseClient; @CustomLog @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ - CredentialsSourceDatabaseProperties.class -}) public class BasicAuthenticationSpringConfig { @Bean @@ -61,9 +57,8 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "file") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") Resource fileName) - throws IOException { - FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName.getURI()); + CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { + FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName); fileCredentialsSource.init(); return fileCredentialsSource; } @@ -71,8 +66,11 @@ CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:cre @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "database") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") - CredentialSource dbCredentialSource(DatabaseClient connectionFactory) { - return new DatabaseCredentialsSource(connectionFactory); + CredentialSource dbCredentialSource( + DatabaseClient databaseClient, + DatabaseProperties databaseProperties, + DatabaseUrlBuilder databaseUrlBuilder) { + return new DatabaseCredentialsSource(databaseClient, databaseUrlBuilder.build(databaseProperties)); } @Bean diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index f6c7a2a7..c11ddbc5 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,5 +1,7 @@ package javasabr.mqtt.auth.credentials.source; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialSource; import lombok.AccessLevel; @@ -20,6 +22,7 @@ public class DatabaseCredentialsSource implements CredentialSource { """; DatabaseClient databaseClient; + String dbUrl; @Override public String getName() { @@ -40,7 +43,6 @@ public Mono isCredentialExists(String username, byte[] requestedPasswor @Override public String toString() { - String driver = databaseClient.getConnectionFactory().getMetadata().getName(); - return "DatabaseCredentialsSource{driver='%s'}".formatted(driver); + return "{ \"%s\": \"%s\" }".formatted(getName(), dbUrl); } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 5089554b..fbd8ca63 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -35,6 +35,6 @@ public String getName() { @Override public String toString() { - return "FileCredentialsSource{fileName='%s'}".formatted(fileName); + return "{ \"%s\": \"%s\" }".formatted(getName(), fileName.getPath()); } } diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java b/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java new file mode 100644 index 00000000..ddf4721d --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java @@ -0,0 +1,26 @@ +package javasabr.mqtt.model; + +import java.time.Duration; + +public interface DatabaseProperties { + String driver(); + + String host(); + + int port(); + + String name(); + + String credentialsQuery(); + + Duration maxIdleTime(); + + int initialPoolSize(); + + int maxPoolSize(); + + String lockTimeout(); + + String statementTimeout(); +} + diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java new file mode 100644 index 00000000..991fb70e --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.model; + +public interface DatabaseUrlBuilder { + String build(DatabaseProperties dbProps); +} From 949b7989f73225cab8f432e0372f7069575d625b Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:44:53 +0100 Subject: [PATCH 022/107] [broker-141] Improve ApplicationPropertiesSpecification initialization --- .../config/MqttBrokerSpringConfig.java | 2 +- .../ApplicationPropertiesSpecification.groovy | 40 +++++++++---------- .../service/AuthenticationServiceTest.groovy | 35 +++++++++------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 78ebb0ff..0b98f26e 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -107,7 +107,7 @@ MqttSessionService mqttSessionService( @Value("${sessions.clean.thread.interval:60000}") int cleanInterval) { return new InMemoryMqttSessionService(cleanInterval); } - + @Bean @ConditionalOnProperty( name = "acl.engine.type", diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy index bc83891c..f13db5b5 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy @@ -1,34 +1,34 @@ package javasabr.mqtt.broker.application import com.hivemq.client.mqtt.MqttClient -import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig import org.springframework.boot.env.PropertiesPropertySourceLoader import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.core.env.PropertySource import org.springframework.core.io.ClassPathResource import spock.lang.Specification -class ApplicationPropertiesSpecification extends Specification { +abstract class ApplicationPropertiesSpecification extends Specification { - def loader = new PropertiesPropertySourceLoader() - def testProperties = loader.load("test-props", new ClassPathResource("application-test.properties")).get(0) + ApplicationContextRunner contextRunner - def contextRunner = new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withInitializer { context -> - context.getEnvironment().getPropertySources().addLast(testProperties) - } - .withUserConfiguration(MqttBrokerTestConfig) - - void mqtt3ClientWithProperties(String[] properties, Closure assertion) { - contextRunner - .withPropertyValues(properties) - .run({ assertion(buildMqtt311Client(it.getBean(InetSocketAddress))) }) + def applyProperties(Class springConfigClass, String applicationPropertiesFile) { + PropertySource propertySource = new PropertiesPropertySourceLoader() + .load("test-props", new ClassPathResource(applicationPropertiesFile)).getFirst() + contextRunner = new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withUserConfiguration(springConfigClass) + .withInitializer { context -> + context.getEnvironment().getPropertySources().addLast(propertySource) + } } - void mqtt5ClientWithProperties(String[] properties, Closure assertion) { + void runContextWithApplicationProperties(String[] properties, Closure clientConstructor, Closure assertion) { + Objects.requireNonNull( + contextRunner, + "ApplicationContextRunner is not initialized. See `ApplicationPropertiesSpecification.applyProperties`") contextRunner .withPropertyValues(properties) - .run({ assertion(buildMqtt5Client(it.getBean(InetSocketAddress))) }) + .run({ assertion(clientConstructor(it.getBean(InetSocketAddress))) }) } def buildMqtt5Client(InetSocketAddress networkAddress) { @@ -66,10 +66,6 @@ class ApplicationPropertiesSpecification extends Specification { } def generateClientId() { - return generateClientId("Default") - } - - def generateClientId(String prefix) { - return prefix + "_" + IntegrationSpecification.idGenerator.incrementAndGet() + return "ApplicationContextRunner_" + IntegrationSpecification.idGenerator.incrementAndGet() } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 4c051634..2604a2d7 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -13,12 +13,17 @@ import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode import javasabr.mqtt.broker.application.ApplicationPropertiesSpecification +import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig import java.nio.charset.StandardCharsets import java.util.concurrent.CompletionException class AuthenticationServiceTest extends ApplicationPropertiesSpecification { + def setup() { + applyProperties(MqttBrokerTestConfig, "application-test.properties") + } + def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { given: def existingUsername = "user" @@ -35,7 +40,7 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { "authentication.credentials.source=$source" } expect: - mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + runContextWithApplicationProperties(authenticationProperties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when try { subscriber.connect(connectMessage).join() @@ -44,7 +49,7 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { } catch (CompletionException e) { assert e.cause instanceof Mqtt3ConnAckException def connAckEx = e.cause as Mqtt3ConnAckException - assert connAckEx.connAck.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + assert connAckEx.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -63,13 +68,13 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { .password(wrongPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when try { subscriber.connect(connectMessage).join() @@ -78,7 +83,7 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { } catch (CompletionException e) { assert e.cause instanceof Mqtt5ConnAckException def connAckEx = e.cause as Mqtt5ConnAckException - assert connAckEx.connAck.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + assert connAckEx.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -98,13 +103,13 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { .password(correctPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + runContextWithApplicationProperties(properties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when Mqtt3ConnAck ack = subscriber.connect(connectMessage).join() // then @@ -129,13 +134,13 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { .password(correctPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when Mqtt5ConnAck ack = subscriber.connect(connectMessage).join() // then @@ -160,13 +165,13 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { .password(correctPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - mqtt5ClientWithProperties(authenticationProperties) { Mqtt5AsyncClient subscriber -> + runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when try { subscriber.connect(connectMessage).join() @@ -175,7 +180,7 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { } catch (CompletionException e) { assert e.cause instanceof Mqtt5ConnAckException def connAckEx = e.cause as Mqtt5ConnAckException - assert connAckEx.connAck.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + assert connAckEx.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -195,13 +200,13 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { .password(correctPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - mqtt3ClientWithProperties(authenticationProperties) { Mqtt3AsyncClient subscriber -> + runContextWithApplicationProperties(properties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when try { subscriber.connect(connectMessage).join() @@ -210,7 +215,7 @@ class AuthenticationServiceTest extends ApplicationPropertiesSpecification { } catch (CompletionException e) { assert e.cause instanceof Mqtt3ConnAckException def connAckEx = e.cause as Mqtt3ConnAckException - assert connAckEx.connAck.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + assert connAckEx.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD } } where: From 3dd963081dfb375089bfc5439ff9068180df6db3 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:07:53 +0100 Subject: [PATCH 023/107] [broker-141] Introduce MqttClientFactory --- ...oovy => ContextRunnerSpecification.groovy} | 35 ++---------- .../IntegrationSpecification.groovy | 56 ++----------------- .../application/MqttClientFactory.groovy | 42 ++++++++++++++ .../service/AuthenticationServiceTest.groovy | 4 +- 4 files changed, 53 insertions(+), 84 deletions(-) rename application/src/test/groovy/javasabr/mqtt/broker/application/{ApplicationPropertiesSpecification.groovy => ContextRunnerSpecification.groovy} (56%) create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy similarity index 56% rename from application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy rename to application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy index f13db5b5..67e6d0f8 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ApplicationPropertiesSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy @@ -1,13 +1,12 @@ package javasabr.mqtt.broker.application -import com.hivemq.client.mqtt.MqttClient import org.springframework.boot.env.PropertiesPropertySourceLoader import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.core.env.PropertySource import org.springframework.core.io.ClassPathResource import spock.lang.Specification -abstract class ApplicationPropertiesSpecification extends Specification { +abstract class ContextRunnerSpecification extends Specification { ApplicationContextRunner contextRunner @@ -32,40 +31,14 @@ abstract class ApplicationPropertiesSpecification extends Specification { } def buildMqtt5Client(InetSocketAddress networkAddress) { - return buildMqtt5Client(generateClientId(), networkAddress) - } - - def buildMqtt5Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion5() - .addDisconnectedListener { - println "[${clientId}|mqtt5] disconnected:[${it.cause.message}]" - } - .build() - .toAsync() + return MqttClientFactory.buildMqtt5Client(generateClientId(), networkAddress) } def buildMqtt311Client(InetSocketAddress networkAddress) { - return buildMqtt311Client(generateClientId(), networkAddress) - } - - def buildMqtt311Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion3() - .addDisconnectedListener { - println "[${clientId}|mqtt311] disconnected:[${it.cause.message}]" - } - .build() - .toAsync() + return MqttClientFactory.buildMqtt311Client(generateClientId(), networkAddress) } def generateClientId() { - return "ApplicationContextRunner_" + IntegrationSpecification.idGenerator.incrementAndGet() + return MqttClientFactory.generateClientId("ApplicationContextRunner") } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy index d50242a3..7b06e438 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy @@ -1,6 +1,5 @@ package javasabr.mqtt.broker.application -import com.hivemq.client.mqtt.MqttClient import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig @@ -17,7 +16,6 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import spock.lang.Specification import java.nio.charset.StandardCharsets -import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference @TestPropertySource("classpath:application-test.properties") @@ -30,8 +28,6 @@ class IntegrationSpecification extends Specification { public static final clientId = "testClientId" public static final keepAlive = 120 - public static final idGenerator = new AtomicInteger(1) - @Autowired InetSocketAddress externalNetworkAddress @@ -39,65 +35,23 @@ class IntegrationSpecification extends Specification { MqttServerConnectionConfig externalConnectionConfig def buildExternalMqtt311Client() { - return buildMqtt311Client(generateClientId(), externalNetworkAddress) + return buildExternalMqtt311Client(generateClientId()) } def buildExternalMqtt5Client() { - return buildMqtt5Client(generateClientId(), externalNetworkAddress) + return buildExternalMqtt5Client(generateClientId()) } def buildExternalMqtt311Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(externalNetworkAddress.getHostName()) - .serverPort(externalNetworkAddress.getPort()) - .useMqttVersion3() - .build() - .toAsync() - } - - def buildMqtt311Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion3() - .addDisconnectedListener { - println "[${clientId}|mqtt311] disconnected:[${it.cause.message}]" - } - .build() - .toAsync() + return MqttClientFactory.buildMqtt311Client(clientId, externalNetworkAddress) } def buildExternalMqtt5Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(externalNetworkAddress.getHostName()) - .serverPort(externalNetworkAddress.getPort()) - .useMqttVersion5() - .build() - .toAsync() - } - - def buildMqtt5Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion5() - .addDisconnectedListener { - println "[${clientId}|mqtt5] disconnected:[${it.cause.message}]" - } - .build() - .toAsync() + return MqttClientFactory.buildMqtt5Client(clientId, externalNetworkAddress) } def generateClientId() { - return generateClientId("Default") - } - - def generateClientId(String prefix) { - return prefix + "_" + idGenerator.incrementAndGet() + return MqttClientFactory.generateClientId("Default") } def connectWith(Mqtt3AsyncClient client, String user, String pass) { diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy new file mode 100644 index 00000000..13372460 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy @@ -0,0 +1,42 @@ +package javasabr.mqtt.broker.application + +import com.hivemq.client.mqtt.MqttClient +import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient + +import java.util.concurrent.atomic.AtomicInteger + +class MqttClientFactory { + + private static final idGenerator = new AtomicInteger(1) + + static Mqtt5AsyncClient buildMqtt5Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion5() + .addDisconnectedListener { + println "[${clientId}|mqtt5] disconnected:[${it.cause.message}]" + } + .build() + .toAsync() + } + + static Mqtt3AsyncClient buildMqtt311Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion3() + .addDisconnectedListener { + println "[${clientId}|mqtt311] disconnected:[${it.cause.message}]" + } + .build() + .toAsync() + } + + static String generateClientId(String prefix) { + return prefix + "_" + idGenerator.incrementAndGet() + } +} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 2604a2d7..ea1e32bd 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -12,13 +12,13 @@ import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import javasabr.mqtt.broker.application.ApplicationPropertiesSpecification +import javasabr.mqtt.broker.application.ContextRunnerSpecification import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig import java.nio.charset.StandardCharsets import java.util.concurrent.CompletionException -class AuthenticationServiceTest extends ApplicationPropertiesSpecification { +class AuthenticationServiceTest extends ContextRunnerSpecification { def setup() { applyProperties(MqttBrokerTestConfig, "application-test.properties") From 97aabb8aad3211b75c5fbdbf873c1882abd92048 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:08:12 +0100 Subject: [PATCH 024/107] [broker-141] Optimize gradle dependencies --- application/build.gradle | 18 +++++++++--------- .../V1__Create_User_Credentials_Table.sql | 0 gradle/libs.versions.toml | 4 ++++ test-support/build.gradle | 2 ++ 4 files changed, 15 insertions(+), 9 deletions(-) rename {credentials-source-db => authentication-service}/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql (100%) diff --git a/application/build.gradle b/application/build.gradle index 7dd85a10..ac5703c2 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -17,20 +17,20 @@ dependencies { implementation libs.rlib.logger.slf4j implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 - implementation libs.jdbc.postgres - implementation libs.flyway.core + + testImplementation projects.testSupport + testImplementation testFixtures(projects.network) + testImplementation projects.credentialsSourceFile + testImplementation projects.credentialsSourceDb + + // database support implementation libs.r2dbc.spi implementation libs.r2dbc.pool implementation libs.r2dbc.postgresql + implementation libs.jdbc.postgres + implementation libs.flyway.core implementation libs.flyway.postgresql - testImplementation 'org.springframework.boot:spring-boot-test:3.2.0' - testImplementation 'org.assertj:assertj-core:3.24.2' - testImplementation libs.r2dbc.h2 - testImplementation projects.credentialsSourceFile - testImplementation projects.credentialsSourceDb - testImplementation projects.testSupport - testImplementation testFixtures(projects.network) } tasks.withType(GroovyCompile).configureEach { diff --git a/credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql similarity index 100% rename from credentials-source-db/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql rename to authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c48e5a82..4d9f6c2e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,8 @@ groovy = "4.0.28" springboot = '3.5.8' # https://mvnrepository.com/artifact/org.springframework/spring-core spring = '6.2.15' +# https://mvnrepository.com/artifact/org.assertj/assertj-core +assertj = '3.24.2' # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy byte-buddy = "1.17.6" # https://mvnrepository.com/artifact/org.objenesis/objenesis @@ -54,6 +56,8 @@ spring-context = { module = "org.springframework:spring-context", version.ref = springboot-starter-core = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot" } springboot-starter-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure", version.ref = "springboot" } springboot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2", version.ref = "springboot" } +springboot-test = { module = "org.springframework.boot:spring-boot-test", version.ref = "springboot" } +assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" } project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" } r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "r2dbc" } r2dbc-spi = { module ='io.r2dbc:r2dbc-spi', version.ref = "r2dbc"} diff --git a/test-support/build.gradle b/test-support/build.gradle index 3762e91c..6a84f39b 100644 --- a/test-support/build.gradle +++ b/test-support/build.gradle @@ -10,6 +10,8 @@ dependencies { api libs.hivemq.mqtt.client api libs.moquette.broker api libs.spring.test + api libs.springboot.test + api libs.assertj.core api libs.spock.core api libs.spock.spring api libs.groovy.all From cbeb2f48146e5696e41a0b954094643e759979c3 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:40:51 +0100 Subject: [PATCH 025/107] [broker-141] Improve AuthenticationServiceTest --- .../ContextRunnerSpecification.groovy | 2 +- .../service/AuthenticationServiceTest.groovy | 60 ++++++++----------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy index 67e6d0f8..fe7fa54c 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy @@ -10,7 +10,7 @@ abstract class ContextRunnerSpecification extends Specification { ApplicationContextRunner contextRunner - def applyProperties(Class springConfigClass, String applicationPropertiesFile) { + def prepareContext(Class springConfigClass, String applicationPropertiesFile) { PropertySource propertySource = new PropertiesPropertySourceLoader() .load("test-props", new ClassPathResource(applicationPropertiesFile)).getFirst() contextRunner = new ApplicationContextRunner() diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index ea1e32bd..97df971b 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -21,7 +21,7 @@ import java.util.concurrent.CompletionException class AuthenticationServiceTest extends ContextRunnerSpecification { def setup() { - applyProperties(MqttBrokerTestConfig, "application-test.properties") + prepareContext(MqttBrokerTestConfig, "application-test.properties") } def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { @@ -42,14 +42,12 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { expect: runContextWithApplicationProperties(authenticationProperties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when - try { - subscriber.connect(connectMessage).join() - throw new AssertionError("MQTT 3 client is able to connect with wrong password" as Object) - // then - } catch (CompletionException e) { - assert e.cause instanceof Mqtt3ConnAckException - def connAckEx = e.cause as Mqtt3ConnAckException - assert connAckEx.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() + // then + assert exception instanceof CompletionException + assert exception.cause instanceof Mqtt3ConnAckException + with(exception.cause as Mqtt3ConnAckException) { + mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -76,14 +74,12 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { expect: runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when - try { - subscriber.connect(connectMessage).join() - throw new AssertionError("MQTT 5 client is able to connect with wrong password" as Object) - // then - } catch (CompletionException e) { - assert e.cause instanceof Mqtt5ConnAckException - def connAckEx = e.cause as Mqtt5ConnAckException - assert connAckEx.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() + // then + assert exception instanceof CompletionException + assert exception.cause instanceof Mqtt5ConnAckException + with(exception.cause as Mqtt5ConnAckException) { + mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -173,14 +169,12 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { expect: runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when - try { - subscriber.connect(connectMessage).join() - throw new AssertionError("MQTT 5 client is able to connect with blank username" as Object) - // then - } catch (CompletionException e) { - assert e.cause instanceof Mqtt5ConnAckException - def connAckEx = e.cause as Mqtt5ConnAckException - assert connAckEx.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() + // then + assert exception instanceof CompletionException + assert exception.cause instanceof Mqtt5ConnAckException + with(exception.cause as Mqtt5ConnAckException) { + mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -208,14 +202,12 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { expect: runContextWithApplicationProperties(properties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when - try { - subscriber.connect(connectMessage).join() - throw new AssertionError("MQTT 5 client is able to connect with blank username" as Object) - // then - } catch (CompletionException e) { - assert e.cause instanceof Mqtt3ConnAckException - def connAckEx = e.cause as Mqtt3ConnAckException - assert connAckEx.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() + // then + assert exception instanceof CompletionException + assert exception.cause instanceof Mqtt3ConnAckException + with(exception.cause as Mqtt3ConnAckException) { + mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD } } where: @@ -223,4 +215,4 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "file" | "basic" "database" | "basic" } -} \ No newline at end of file +} From ce09c2f9768057e29b7e0c43fc424543194cf1f9 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:58:24 +0100 Subject: [PATCH 026/107] [broker-141] Increase password length --- .../db/migration/V1__Create_User_Credentials_Table.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql index cb8b7aec..972a7b9d 100644 --- a/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql +++ b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql @@ -1,4 +1,4 @@ CREATE TABLE user_credentials ( username VARCHAR(255), - password VARBINARY(20) + password VARBINARY(255) ); From 677958e68ee01b86db9a0022ff9446ca455bd823 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:43:56 +0100 Subject: [PATCH 027/107] [broker-141] Refactoring --- .../db/DatabaseConnectionProperties.java | 3 ++- .../ContextRunnerSpecification.groovy | 20 ++++++------------- .../application/ExternalConnectionTest.groovy | 7 ------- .../config/CredentialsSourceTestConfig.groovy | 2 +- .../service/AuthenticationServiceTest.groovy | 15 +++++++------- .../mqtt/model/DatabaseUrlBuilder.java | 2 +- 6 files changed, 18 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java index e7d5f7bb..b7cf2236 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java @@ -1,7 +1,8 @@ package javasabr.mqtt.broker.application.config.db; import java.time.Duration; -import javasabr.mqtt.model.DatabaseProperties;import org.springframework.boot.context.properties.ConfigurationProperties; +import javasabr.mqtt.model.DatabaseProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "credentials.source.db") public record DatabaseConnectionProperties( diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy index fe7fa54c..e9e08c6d 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy @@ -6,6 +6,8 @@ import org.springframework.core.env.PropertySource import org.springframework.core.io.ClassPathResource import spock.lang.Specification +import static javasabr.mqtt.broker.application.MqttClientFactory.generateClientId + abstract class ContextRunnerSpecification extends Specification { ApplicationContextRunner contextRunner @@ -21,24 +23,14 @@ abstract class ContextRunnerSpecification extends Specification { } } - void runContextWithApplicationProperties(String[] properties, Closure clientConstructor, Closure assertion) { + void runContextWithProperties(String[] properties, Closure clientConstructor, Closure assertion) { Objects.requireNonNull( contextRunner, "ApplicationContextRunner is not initialized. See `ApplicationPropertiesSpecification.applyProperties`") contextRunner .withPropertyValues(properties) - .run({ assertion(clientConstructor(it.getBean(InetSocketAddress))) }) - } - - def buildMqtt5Client(InetSocketAddress networkAddress) { - return MqttClientFactory.buildMqtt5Client(generateClientId(), networkAddress) - } - - def buildMqtt311Client(InetSocketAddress networkAddress) { - return MqttClientFactory.buildMqtt311Client(generateClientId(), networkAddress) - } - - def generateClientId() { - return MqttClientFactory.generateClientId("ApplicationContextRunner") + .run({ ctx -> + assertion(clientConstructor(generateClientId("ApplicationContextRunner"), ctx.getBean(InetSocketAddress))) + }) } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy index bc7708e3..bf46d0fc 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy @@ -5,14 +5,7 @@ import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCo import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode import javasabr.mqtt.model.MqttProperties -import javasabr.mqtt.model.QoS -import javasabr.mqtt.model.reason.code.ConnectAckReasonCode -import javasabr.mqtt.network.message.in.ConnectAckMqttInMessage -import javasabr.mqtt.network.message.out.ConnectMqtt311OutMessage -import javasabr.rlib.common.util.ArrayUtils -import spock.lang.Ignore -import java.nio.charset.StandardCharsets import java.util.concurrent.CompletionException class ExternalConnectionTest extends IntegrationSpecification { diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy index 7bdd49d1..b6a4dcf9 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy @@ -36,6 +36,6 @@ class CredentialsSourceTestConfig { @Bean DatabaseUrlBuilder databaseUrlBuilder() { - return { dbProps -> "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" } + return { "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" } } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 97df971b..3e6e79aa 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -13,6 +13,7 @@ import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode import javasabr.mqtt.broker.application.ContextRunnerSpecification +import javasabr.mqtt.broker.application.MqttClientFactory import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig import java.nio.charset.StandardCharsets @@ -34,13 +35,13 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { .password(wrongPassword) .build()) .build() - String[] authenticationProperties = new String[]{ + String[] properties = new String[]{ "authentication.allow.anonymous=false", "authentication.provider=$provider", "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(authenticationProperties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() // then @@ -72,7 +73,7 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() // then @@ -105,7 +106,7 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(properties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when Mqtt3ConnAck ack = subscriber.connect(connectMessage).join() // then @@ -136,7 +137,7 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when Mqtt5ConnAck ack = subscriber.connect(connectMessage).join() // then @@ -167,7 +168,7 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(properties, this.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> // when def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() // then @@ -200,7 +201,7 @@ class AuthenticationServiceTest extends ContextRunnerSpecification { "authentication.credentials.source=$source" } expect: - runContextWithApplicationProperties(properties, this.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> + runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> // when def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() // then diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java index 991fb70e..9f2581eb 100644 --- a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java +++ b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java @@ -1,5 +1,5 @@ package javasabr.mqtt.model; public interface DatabaseUrlBuilder { - String build(DatabaseProperties dbProps); + String build(DatabaseProperties databaseProperties); } From 632737bab49ba36b6cd9b461fde9642e502ecce2 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:25:23 +0100 Subject: [PATCH 028/107] [broker-141] Move db user credentials into map --- .../DatabaseConnectionProperties.java | 6 ++-- .../config/DatabaseSpringConfig.java | 28 ++++++------------- .../credentials/DatabaseAdminCredential.java | 6 ---- .../credentials/DatabaseReaderCredential.java | 6 ---- .../credentials/DatabaseWriterCredential.java | 6 ---- .../config/CredentialsSourceTestConfig.groovy | 8 +++--- .../resources/application-test.properties | 26 ++++++++--------- .../provider/BasicAuthenticationProvider.java | 6 ++-- .../mqtt/auth/api/CredentialSource.java | 10 ------- .../mqtt/auth/api/CredentialsSource.java | 10 +++++++ ...ce.java => InMemoryCredentialsSource.java} | 4 +-- .../BasicAuthenticationSpringConfig.java | 10 +++---- .../source/DatabaseCredentialsSource.java | 8 ++---- .../source/FileCredentialsSource.java | 4 +-- .../java/javasabr/mqtt/model/Credentials.java | 3 ++ 15 files changed, 57 insertions(+), 84 deletions(-) rename application/src/main/java/javasabr/mqtt/broker/application/config/{db => }/DatabaseConnectionProperties.java (70%) delete mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java delete mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java delete mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java delete mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{InMemoryCredentialSource.java => InMemoryCredentialsSource.java} (91%) create mode 100644 model/src/main/java/javasabr/mqtt/model/Credentials.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java similarity index 70% rename from application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java rename to application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java index b7cf2236..18267785 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/db/DatabaseConnectionProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java @@ -1,11 +1,13 @@ -package javasabr.mqtt.broker.application.config.db; +package javasabr.mqtt.broker.application.config; import java.time.Duration; +import java.util.Map; import javasabr.mqtt.model.DatabaseProperties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "credentials.source.db") +@ConfigurationProperties(prefix = "persistence.database") public record DatabaseConnectionProperties( + Map users, String driver, String host, int port, diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java index 95f131e7..8d61ab63 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -14,10 +14,6 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.broker.application.config.db.DatabaseConnectionProperties; -import javasabr.mqtt.broker.application.config.db.credentials.DatabaseAdminCredential; -import javasabr.mqtt.broker.application.config.db.credentials.DatabaseReaderCredential; -import javasabr.mqtt.broker.application.config.db.credentials.DatabaseWriterCredential; import javasabr.mqtt.model.DatabaseUrlBuilder; import org.flywaydb.core.Flyway; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -26,19 +22,13 @@ import org.springframework.context.annotation.DependsOn; @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ - DatabaseConnectionProperties.class, - DatabaseAdminCredential.class, - DatabaseReaderCredential.class, - DatabaseWriterCredential.class -}) +@EnableConfigurationProperties(DatabaseConnectionProperties.class) public class DatabaseSpringConfig { @Bean @DependsOn("flyway") - ConnectionFactory connectionFactory( - DatabaseConnectionProperties config, - DatabaseWriterCredential writerCredential) { + ConnectionFactory connectionFactory(DatabaseConnectionProperties config) { + javasabr.mqtt.model.Credentials reader = config.users().get("reader"); Map timeoutOptions = Map.of( "lock_timeout", config.lockTimeout(), "statement_timeout", config.statementTimeout()); @@ -46,8 +36,8 @@ ConnectionFactory connectionFactory( .option(DRIVER, config.driver()) .option(HOST, config.host()) .option(PORT, config.port()) - .option(USER, writerCredential.username()) - .option(PASSWORD, writerCredential.password()) + .option(USER, reader.username()) + .option(PASSWORD, reader.password()) .option(DATABASE, config.name()) .option(OPTIONS, timeoutOptions).build(); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); @@ -60,12 +50,10 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - Flyway flyway( - DatabaseUrlBuilder databaseUrlBuilder, - DatabaseConnectionProperties dbProperties, - DatabaseAdminCredential adminCredential) { + Flyway flyway(DatabaseUrlBuilder databaseUrlBuilder, DatabaseConnectionProperties dbProperties) { + javasabr.mqtt.model.Credentials admin = dbProperties.users().get("admin"); return Flyway.configure() - .dataSource(databaseUrlBuilder.build(dbProperties), adminCredential.username(), adminCredential.password()) + .dataSource(databaseUrlBuilder.build(dbProperties), admin.username(), admin.password()) .load(); } diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java deleted file mode 100644 index 4c80766b..00000000 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseAdminCredential.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.broker.application.config.db.credentials; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "credentials.source.db.admin") -public record DatabaseAdminCredential(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java deleted file mode 100644 index 046894f5..00000000 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseReaderCredential.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.broker.application.config.db.credentials; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "credentials.source.db.reader") -public record DatabaseReaderCredential(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java b/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java deleted file mode 100644 index 57aa2db7..00000000 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/db/credentials/DatabaseWriterCredential.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.broker.application.config.db.credentials; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "credentials.source.db.writer") -public record DatabaseWriterCredential(String username, String password) {} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy index b6a4dcf9..0d96f1d2 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy @@ -4,7 +4,6 @@ import io.r2dbc.spi.ConnectionFactories import io.r2dbc.spi.ConnectionFactory import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option -import javasabr.mqtt.broker.application.config.db.credentials.DatabaseWriterCredential import javasabr.mqtt.model.DatabaseUrlBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -22,13 +21,14 @@ class CredentialsSourceTestConfig { @Bean @DependsOn("flyway") - ConnectionFactory connectionFactory(DatabaseWriterCredential writerCredential) { + ConnectionFactory connectionFactory(DatabaseConnectionProperties dbProperties) { + def reader = dbProperties.users().get("reader") ConnectionFactoryOptions options = builder() .option(DRIVER, "h2") .option(PROTOCOL, "mem") .option(DATABASE, "testdb") - .option(USER, writerCredential.username()) - .option(PASSWORD, writerCredential.password()) + .option(USER, reader.username()) + .option(PASSWORD, reader.password()) .option(Option.valueOf("DB_CLOSE_DELAY"), "-1") .build() return ConnectionFactories.get(options) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 7fb20ef7..3d68da0e 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -2,16 +2,16 @@ authentication.allow.anonymous=true credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true -credentials.source.db.driver=h2 -credentials.source.db.host=localhost -credentials.source.db.port=5432 -credentials.source.db.name=database-name -credentials.source.db.max-idle-time=30m -credentials.source.db.initial-pool-size=5 -credentials.source.db.max-pool-size=10 -credentials.source.db.lock-timeout=10s -credentials.source.db.statement-timeout=5m -credentials.source.db.admin.username=user -credentials.source.db.admin.password= -credentials.source.db.writer.username=user -credentials.source.db.writer.password= +persistence.database.driver=postgresql +persistence.database.host=localhost +persistence.database.port=5432 +persistence.database.name=database-name +persistence.database.max-idle-time=30m +persistence.database.initial-pool-size=5 +persistence.database.max-pool-size=10 +persistence.database.lock-timeout=10s +persistence.database.statement-timeout=5m +persistence.database.users.admin.username=user +persistence.database.users.admin.password= +persistence.database.users.reader.username=user +persistence.database.users.reader.password= diff --git a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index e456ecb4..764c3c4f 100644 --- a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -1,7 +1,7 @@ package javasabr.mqtt.auth.provider; import javasabr.mqtt.auth.api.AuthenticationProvider; -import javasabr.mqtt.auth.api.CredentialSource; +import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -12,7 +12,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class BasicAuthenticationProvider implements AuthenticationProvider { - CredentialSource credentialsSource; + CredentialsSource credentialsSource; @Override public String getName() { @@ -24,7 +24,7 @@ public Mono authenticate(@Nullable String username, byte[] password, by if (username == null) { return Mono.just(false); } else { - return credentialsSource.isCredentialExists(username, password); + return credentialsSource.isCredentialsExists(username, password); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java deleted file mode 100644 index f11c5251..00000000 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialSource.java +++ /dev/null @@ -1,10 +0,0 @@ -package javasabr.mqtt.auth.api; - -import reactor.core.publisher.Mono; - -public interface CredentialSource { - - String getName(); - - Mono isCredentialExists(String user, byte[] pass); -} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java new file mode 100644 index 00000000..3f0cf2ca --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.auth.api; + +import reactor.core.publisher.Mono; + +public interface CredentialsSource { + + String getName(); + + Mono isCredentialsExists(String user, byte[] pass); +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java similarity index 91% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java index 3f60ad38..b5a797bd 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java @@ -11,7 +11,7 @@ import javasabr.rlib.collections.dictionary.RefToRefDictionary; import reactor.core.publisher.Mono; -public abstract class InMemoryCredentialSource implements CredentialSource { +public abstract class InMemoryCredentialsSource implements CredentialsSource { private final LockableRefToRefDictionary credentials = DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); @@ -47,7 +47,7 @@ protected void reset(InputStream inStream) throws IOException { } @Override - public Mono isCredentialExists(String user, byte[] pass) { + public Mono isCredentialsExists(String user, byte[] pass) { return Mono.just(Arrays.equals(pass, credentials.get(user))); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index 6fa25476..8887100c 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -7,7 +7,7 @@ import javasabr.mqtt.auth.api.AuthenticationConfigException; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.api.CredentialSource; +import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; @@ -57,7 +57,7 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "file") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { + CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName); fileCredentialsSource.init(); return fileCredentialsSource; @@ -66,7 +66,7 @@ CredentialSource fileCredentialSource(@Value("${credentials.source.file.name:cre @Bean @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "database") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") - CredentialSource dbCredentialSource( + CredentialsSource dbCredentialsSource( DatabaseClient databaseClient, DatabaseProperties databaseProperties, DatabaseUrlBuilder databaseUrlBuilder) { @@ -76,8 +76,8 @@ CredentialSource dbCredentialSource( @Bean @ConditionalOnProperty(name = "authentication.provider", havingValue = "basic") @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") - AuthenticationProvider passwordBasedAuthenticationProvider(CredentialSource credentialSource) { - return new BasicAuthenticationProvider(credentialSource); + AuthenticationProvider passwordBasedAuthenticationProvider(CredentialsSource credentialsSource) { + return new BasicAuthenticationProvider(credentialsSource); } @Bean diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index c11ddbc5..cb1ee97e 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,9 +1,7 @@ package javasabr.mqtt.auth.credentials.source; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactory; import java.util.Arrays; -import javasabr.mqtt.auth.api.CredentialSource; +import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -12,7 +10,7 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class DatabaseCredentialsSource implements CredentialSource { +public class DatabaseCredentialsSource implements CredentialsSource { @SuppressWarnings("SqlNoDataSourceInspection") private static final String CREDENTIALS_QUERY = """ @@ -30,7 +28,7 @@ public String getName() { } @Override - public Mono isCredentialExists(String username, byte[] requestedPassword) { + public Mono isCredentialsExists(String username, byte[] requestedPassword) { return databaseClient .sql(CREDENTIALS_QUERY) .bind("$1", username) diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index fbd8ca63..fbb558e0 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -5,14 +5,14 @@ import java.nio.file.Files; import java.nio.file.Path; import javasabr.mqtt.auth.api.CredentialsSourceException; -import javasabr.mqtt.auth.api.InMemoryCredentialSource; +import javasabr.mqtt.auth.api.InMemoryCredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class FileCredentialsSource extends InMemoryCredentialSource { +public class FileCredentialsSource extends InMemoryCredentialsSource { URI fileName; diff --git a/model/src/main/java/javasabr/mqtt/model/Credentials.java b/model/src/main/java/javasabr/mqtt/model/Credentials.java new file mode 100644 index 00000000..d55cbb7c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/Credentials.java @@ -0,0 +1,3 @@ +package javasabr.mqtt.model; + +public record Credentials(String username, String password) {} From b33867009ad42a91fa4bd208c7878f1a26e97e9e Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:03:03 +0100 Subject: [PATCH 029/107] [broker-141] Refactoring of properties access --- .../config/DatabaseConnectionProperties.java | 11 +++- .../config/DatabaseSpringConfig.java | 59 ++++++++++++++----- .../V1__Create_User_Credentials_Table.sql | 0 ...groovy => DatabaseTestSpringConfig.groovy} | 20 +++---- .../config/MqttBrokerTestConfig.groovy | 3 +- .../BasicAuthenticationSpringConfig.java | 4 +- .../mqtt/model/DatabaseProperties.java | 26 -------- .../mqtt/model/DatabaseUrlBuilder.java | 4 +- .../database/DatabasePoolProperties.java | 13 ++++ .../database/DatabaseTimeoutsProperties.java | 9 +++ .../model/database/DatabaseUrlProperties.java | 14 +++++ .../database/DatabaseUsersProperties.java | 16 +++++ 12 files changed, 120 insertions(+), 59 deletions(-) rename {authentication-service => application}/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql (100%) rename application/src/test/groovy/javasabr/mqtt/broker/application/config/{CredentialsSourceTestConfig.groovy => DatabaseTestSpringConfig.groovy} (61%) delete mode 100644 model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java create mode 100644 model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java create mode 100644 model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java create mode 100644 model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java create mode 100644 model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java index 18267785..6adfb416 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java @@ -2,12 +2,16 @@ import java.time.Duration; import java.util.Map; -import javasabr.mqtt.model.DatabaseProperties; +import javasabr.mqtt.model.Credentials; +import javasabr.mqtt.model.database.DatabasePoolProperties; +import javasabr.mqtt.model.database.DatabaseTimeoutsProperties; +import javasabr.mqtt.model.database.DatabaseUrlProperties; +import javasabr.mqtt.model.database.DatabaseUsersProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "persistence.database") public record DatabaseConnectionProperties( - Map users, + Map users, String driver, String host, int port, @@ -17,4 +21,5 @@ public record DatabaseConnectionProperties( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) implements DatabaseProperties {} + String statementTimeout +) implements DatabasePoolProperties, DatabaseUrlProperties, DatabaseUsersProperties, DatabaseTimeoutsProperties {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java index 8d61ab63..a1e190f7 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -14,8 +14,14 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; +import javasabr.mqtt.model.Credentials; import javasabr.mqtt.model.DatabaseUrlBuilder; +import javasabr.mqtt.model.database.DatabasePoolProperties; +import javasabr.mqtt.model.database.DatabaseTimeoutsProperties; +import javasabr.mqtt.model.database.DatabaseUrlProperties; +import javasabr.mqtt.model.database.DatabaseUsersProperties; import org.flywaydb.core.Flyway; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,20 +32,28 @@ public class DatabaseSpringConfig { @Bean - @DependsOn("flyway") - ConnectionFactory connectionFactory(DatabaseConnectionProperties config) { - javasabr.mqtt.model.Credentials reader = config.users().get("reader"); + ConnectionFactoryOptions connectionFactoryOptions( + DatabaseTimeoutsProperties config, + DatabaseUrlProperties databaseUrlProperties, + @Qualifier("readerCredentials") Credentials credentials){ Map timeoutOptions = Map.of( "lock_timeout", config.lockTimeout(), "statement_timeout", config.statementTimeout()); - ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() - .option(DRIVER, config.driver()) - .option(HOST, config.host()) - .option(PORT, config.port()) - .option(USER, reader.username()) - .option(PASSWORD, reader.password()) - .option(DATABASE, config.name()) + return ConnectionFactoryOptions.builder() + .option(DRIVER, databaseUrlProperties.driver()) + .option(HOST, databaseUrlProperties.host()) + .option(PORT, databaseUrlProperties.port()) + .option(USER, credentials.username()) + .option(PASSWORD, credentials.password()) + .option(DATABASE, databaseUrlProperties.name()) .option(OPTIONS, timeoutOptions).build(); + } + + @Bean + @DependsOn("flyway") + ConnectionFactory connectionFactory( + DatabasePoolProperties config, + ConnectionFactoryOptions connectionFactoryOptions) { ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) .maxIdleTime(config.maxIdleTime()) @@ -50,15 +64,32 @@ ConnectionFactory connectionFactory(DatabaseConnectionProperties config) { } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseUrlBuilder databaseUrlBuilder, DatabaseConnectionProperties dbProperties) { - javasabr.mqtt.model.Credentials admin = dbProperties.users().get("admin"); + Flyway flyway( + DatabaseUrlBuilder databaseUrlBuilder, + DatabaseUrlProperties databaseUrlProperties, + @Qualifier("adminCredentials") Credentials credentials) { return Flyway.configure() - .dataSource(databaseUrlBuilder.build(dbProperties), admin.username(), admin.password()) + .dataSource(databaseUrlBuilder.build(databaseUrlProperties), credentials.username(), credentials.password()) .load(); } @Bean DatabaseUrlBuilder databaseUrlBuilder() { - return dbProps -> "jdbc:%s://%s:%s/%s".formatted(dbProps.driver(), dbProps.host(), dbProps.port(), dbProps.name()); + return db -> "jdbc:%s://%s:%s/%s".formatted(db.driver(), db.host(), db.port(), db.name()); + } + + @Bean + public Credentials readerCredentials(DatabaseUsersProperties users) { + return users.get("reader"); + } + + @Bean + public Credentials writerCredentials(DatabaseUsersProperties users) { + return users.get("writer"); + } + + @Bean + public Credentials adminCredentials(DatabaseUsersProperties users) { + return users.get("admin"); } } diff --git a/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql similarity index 100% rename from authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql rename to application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy similarity index 61% rename from application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy rename to application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy index 0d96f1d2..12e2f31c 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/CredentialsSourceTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy @@ -1,37 +1,33 @@ +//file:noinspection SpringJavaInjectionPointsAutowiringInspection package javasabr.mqtt.broker.application.config -import io.r2dbc.spi.ConnectionFactories -import io.r2dbc.spi.ConnectionFactory import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option +import javasabr.mqtt.model.Credentials import javasabr.mqtt.model.DatabaseUrlBuilder +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.DependsOn import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL import static io.r2dbc.spi.ConnectionFactoryOptions.USER -import static io.r2dbc.spi.ConnectionFactoryOptions.builder @Configuration -class CredentialsSourceTestConfig { +class DatabaseTestSpringConfig { @Bean - @DependsOn("flyway") - ConnectionFactory connectionFactory(DatabaseConnectionProperties dbProperties) { - def reader = dbProperties.users().get("reader") - ConnectionFactoryOptions options = builder() + ConnectionFactoryOptions connectionFactoryOptions(@Qualifier("readerCredentials") Credentials credentials) { + return ConnectionFactoryOptions.builder() .option(DRIVER, "h2") .option(PROTOCOL, "mem") .option(DATABASE, "testdb") - .option(USER, reader.username()) - .option(PASSWORD, reader.password()) + .option(USER, credentials.username()) + .option(PASSWORD, credentials.password()) .option(Option.valueOf("DB_CLOSE_DELAY"), "-1") .build() - return ConnectionFactories.get(options) } @Bean diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy index cc61c748..d685b841 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy @@ -1,3 +1,4 @@ +//file:noinspection SpringJavaInjectionPointsAutowiringInspection package javasabr.mqtt.broker.application.config import javasabr.mqtt.network.MqttConnection @@ -13,7 +14,7 @@ import java.util.concurrent.ThreadLocalRandom @Import([ MqttBrokerSpringConfig, - CredentialsSourceTestConfig + DatabaseTestSpringConfig ]) @Configuration(proxyBeanMethods = false) class MqttBrokerTestConfig { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index 8887100c..3d75655e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -12,8 +12,8 @@ import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.model.DatabaseProperties; import javasabr.mqtt.model.DatabaseUrlBuilder; +import javasabr.mqtt.model.database.DatabaseUrlProperties; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; @@ -68,7 +68,7 @@ CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:c @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") CredentialsSource dbCredentialsSource( DatabaseClient databaseClient, - DatabaseProperties databaseProperties, + DatabaseUrlProperties databaseProperties, DatabaseUrlBuilder databaseUrlBuilder) { return new DatabaseCredentialsSource(databaseClient, databaseUrlBuilder.build(databaseProperties)); } diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java b/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java deleted file mode 100644 index ddf4721d..00000000 --- a/model/src/main/java/javasabr/mqtt/model/DatabaseProperties.java +++ /dev/null @@ -1,26 +0,0 @@ -package javasabr.mqtt.model; - -import java.time.Duration; - -public interface DatabaseProperties { - String driver(); - - String host(); - - int port(); - - String name(); - - String credentialsQuery(); - - Duration maxIdleTime(); - - int initialPoolSize(); - - int maxPoolSize(); - - String lockTimeout(); - - String statementTimeout(); -} - diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java index 9f2581eb..fbe39071 100644 --- a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java +++ b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java @@ -1,5 +1,7 @@ package javasabr.mqtt.model; +import javasabr.mqtt.model.database.DatabaseUrlProperties; + public interface DatabaseUrlBuilder { - String build(DatabaseProperties databaseProperties); + String build(DatabaseUrlProperties databaseProperties); } diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java new file mode 100644 index 00000000..a60ccb32 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java @@ -0,0 +1,13 @@ +package javasabr.mqtt.model.database; + +import java.time.Duration; + +public interface DatabasePoolProperties { + + Duration maxIdleTime(); + + int initialPoolSize(); + + int maxPoolSize(); +} + diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java new file mode 100644 index 00000000..3d93c23f --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java @@ -0,0 +1,9 @@ +package javasabr.mqtt.model.database; + +public interface DatabaseTimeoutsProperties { + + String lockTimeout(); + + String statementTimeout(); +} + diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java new file mode 100644 index 00000000..b97e6944 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.model.database; + +import java.time.Duration; + +public interface DatabaseUrlProperties { + String driver(); + + String host(); + + int port(); + + String name(); +} + diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java new file mode 100644 index 00000000..32627853 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.model.database; + +import java.util.Map; +import javasabr.mqtt.model.Credentials; + +public interface DatabaseUsersProperties { + + Credentials ANONYMOUS_CREDENTIAL = new Credentials("", ""); + + Map users(); + + default Credentials get(String username) { + return users().getOrDefault(username, ANONYMOUS_CREDENTIAL); + } +} + From 858b244812623fbe06885f99e60a0569bcb180f2 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:42:33 +0100 Subject: [PATCH 030/107] [broker-141] Rename createContextRunner method --- .../mqtt/broker/application/ContextRunnerSpecification.groovy | 4 ++-- .../application/service/AuthenticationServiceTest.groovy | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy index e9e08c6d..e0b3cf8e 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy @@ -12,7 +12,7 @@ abstract class ContextRunnerSpecification extends Specification { ApplicationContextRunner contextRunner - def prepareContext(Class springConfigClass, String applicationPropertiesFile) { + def createContextRunner(Class springConfigClass, String applicationPropertiesFile) { PropertySource propertySource = new PropertiesPropertySourceLoader() .load("test-props", new ClassPathResource(applicationPropertiesFile)).getFirst() contextRunner = new ApplicationContextRunner() @@ -26,7 +26,7 @@ abstract class ContextRunnerSpecification extends Specification { void runContextWithProperties(String[] properties, Closure clientConstructor, Closure assertion) { Objects.requireNonNull( contextRunner, - "ApplicationContextRunner is not initialized. See `ApplicationPropertiesSpecification.applyProperties`") + "ApplicationContextRunner is not initialized. See `ApplicationPropertiesSpecification.createContextRunner`") contextRunner .withPropertyValues(properties) .run({ ctx -> diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 3e6e79aa..fa552cbc 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -22,7 +22,7 @@ import java.util.concurrent.CompletionException class AuthenticationServiceTest extends ContextRunnerSpecification { def setup() { - prepareContext(MqttBrokerTestConfig, "application-test.properties") + createContextRunner(MqttBrokerTestConfig, "application-test.properties") } def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { From b4c636c4b0779a228561ede67d1e605fa8e2cf8b Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:57:37 +0100 Subject: [PATCH 031/107] [broker-141] Rename classes and variables --- .../config/DatabaseConnectionProperties.java | 10 ++-- .../config/DatabaseSpringConfig.java | 47 ++++++++++--------- .../config/DatabaseTestSpringConfig.groovy | 2 +- .../service/DefaultAuthenticationService.java | 8 ++-- .../BasicAuthenticationSpringConfig.java | 8 ++-- .../mqtt/model/DatabaseUrlBuilder.java | 7 --- ...roperties.java => DatabasePoolConfig.java} | 2 +- ...rties.java => DatabaseTimeoutsConfig.java} | 2 +- .../model/database/DatabaseUrlBuilder.java | 5 ++ ...Properties.java => DatabaseUrlConfig.java} | 4 +- ...operties.java => DatabaseUsersConfig.java} | 2 +- 11 files changed, 48 insertions(+), 49 deletions(-) delete mode 100644 model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java rename model/src/main/java/javasabr/mqtt/model/database/{DatabasePoolProperties.java => DatabasePoolConfig.java} (77%) rename model/src/main/java/javasabr/mqtt/model/database/{DatabaseTimeoutsProperties.java => DatabaseTimeoutsConfig.java} (67%) create mode 100644 model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java rename model/src/main/java/javasabr/mqtt/model/database/{DatabaseUrlProperties.java => DatabaseUrlConfig.java} (61%) rename model/src/main/java/javasabr/mqtt/model/database/{DatabaseUsersProperties.java => DatabaseUsersConfig.java} (88%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java index 6adfb416..6a6631fa 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java @@ -3,10 +3,10 @@ import java.time.Duration; import java.util.Map; import javasabr.mqtt.model.Credentials; -import javasabr.mqtt.model.database.DatabasePoolProperties; -import javasabr.mqtt.model.database.DatabaseTimeoutsProperties; -import javasabr.mqtt.model.database.DatabaseUrlProperties; -import javasabr.mqtt.model.database.DatabaseUsersProperties; +import javasabr.mqtt.model.database.DatabasePoolConfig; +import javasabr.mqtt.model.database.DatabaseTimeoutsConfig; +import javasabr.mqtt.model.database.DatabaseUrlConfig; +import javasabr.mqtt.model.database.DatabaseUsersConfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "persistence.database") @@ -22,4 +22,4 @@ public record DatabaseConnectionProperties( int maxPoolSize, String lockTimeout, String statementTimeout -) implements DatabasePoolProperties, DatabaseUrlProperties, DatabaseUsersProperties, DatabaseTimeoutsProperties {} +) implements DatabasePoolConfig, DatabaseUrlConfig, DatabaseUsersConfig, DatabaseTimeoutsConfig {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java index a1e190f7..a4101544 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -15,11 +15,11 @@ import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; import javasabr.mqtt.model.Credentials; -import javasabr.mqtt.model.DatabaseUrlBuilder; -import javasabr.mqtt.model.database.DatabasePoolProperties; -import javasabr.mqtt.model.database.DatabaseTimeoutsProperties; -import javasabr.mqtt.model.database.DatabaseUrlProperties; -import javasabr.mqtt.model.database.DatabaseUsersProperties; +import javasabr.mqtt.model.database.DatabaseUrlBuilder; +import javasabr.mqtt.model.database.DatabasePoolConfig; +import javasabr.mqtt.model.database.DatabaseTimeoutsConfig; +import javasabr.mqtt.model.database.DatabaseUrlConfig; +import javasabr.mqtt.model.database.DatabaseUsersConfig; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -33,32 +33,33 @@ public class DatabaseSpringConfig { @Bean ConnectionFactoryOptions connectionFactoryOptions( - DatabaseTimeoutsProperties config, - DatabaseUrlProperties databaseUrlProperties, + DatabaseTimeoutsConfig databaseTimeoutsConfig, + DatabaseUrlConfig databaseUrlConfig, @Qualifier("readerCredentials") Credentials credentials){ Map timeoutOptions = Map.of( - "lock_timeout", config.lockTimeout(), - "statement_timeout", config.statementTimeout()); + "lock_timeout", databaseTimeoutsConfig.lockTimeout(), + "statement_timeout", databaseTimeoutsConfig.statementTimeout()); return ConnectionFactoryOptions.builder() - .option(DRIVER, databaseUrlProperties.driver()) - .option(HOST, databaseUrlProperties.host()) - .option(PORT, databaseUrlProperties.port()) + .option(DATABASE, databaseUrlConfig.name()) + .option(DRIVER, databaseUrlConfig.driver()) + .option(HOST, databaseUrlConfig.host()) + .option(PORT, databaseUrlConfig.port()) .option(USER, credentials.username()) .option(PASSWORD, credentials.password()) - .option(DATABASE, databaseUrlProperties.name()) - .option(OPTIONS, timeoutOptions).build(); + .option(OPTIONS, timeoutOptions) + .build(); } @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - DatabasePoolProperties config, + DatabasePoolConfig databasePoolConfig, ConnectionFactoryOptions connectionFactoryOptions) { ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(config.maxIdleTime()) - .maxSize(config.maxPoolSize()) - .initialSize(config.initialPoolSize()) + .maxIdleTime(databasePoolConfig.maxIdleTime()) + .maxSize(databasePoolConfig.maxPoolSize()) + .initialSize(databasePoolConfig.initialPoolSize()) .build(); return new ConnectionPool(configuration); } @@ -66,10 +67,10 @@ ConnectionFactory connectionFactory( @Bean(initMethod = "migrate") Flyway flyway( DatabaseUrlBuilder databaseUrlBuilder, - DatabaseUrlProperties databaseUrlProperties, + DatabaseUrlConfig databaseUrlConfig, @Qualifier("adminCredentials") Credentials credentials) { return Flyway.configure() - .dataSource(databaseUrlBuilder.build(databaseUrlProperties), credentials.username(), credentials.password()) + .dataSource(databaseUrlBuilder.build(databaseUrlConfig), credentials.username(), credentials.password()) .load(); } @@ -79,17 +80,17 @@ DatabaseUrlBuilder databaseUrlBuilder() { } @Bean - public Credentials readerCredentials(DatabaseUsersProperties users) { + public Credentials readerCredentials(DatabaseUsersConfig users) { return users.get("reader"); } @Bean - public Credentials writerCredentials(DatabaseUsersProperties users) { + public Credentials writerCredentials(DatabaseUsersConfig users) { return users.get("writer"); } @Bean - public Credentials adminCredentials(DatabaseUsersProperties users) { + public Credentials adminCredentials(DatabaseUsersConfig users) { return users.get("admin"); } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy index 12e2f31c..087fa90b 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy @@ -4,7 +4,7 @@ package javasabr.mqtt.broker.application.config import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option import javasabr.mqtt.model.Credentials -import javasabr.mqtt.model.DatabaseUrlBuilder +import javasabr.mqtt.model.database.DatabaseUrlBuilder import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index f65203e1..3e663d6d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -35,9 +35,11 @@ private static String buildServiceDescription( RefToRefDictionary providers, AuthenticationProvider defaultProvider) { - var builder = new StringBuilder(); - builder.append("{\n"); - builder.append(" \"DEFAULT\": \"").append(defaultProvider.getName()).append("\",\n"); + var builder = new StringBuilder() + .append("{\n") + .append(" \"DEFAULT\": \"") + .append(defaultProvider.getName()) + .append("\",\n"); for (AuthenticationProvider provider : providers) { builder diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index 3d75655e..c22619e5 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -12,8 +12,8 @@ import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.model.DatabaseUrlBuilder; -import javasabr.mqtt.model.database.DatabaseUrlProperties; +import javasabr.mqtt.model.database.DatabaseUrlBuilder; +import javasabr.mqtt.model.database.DatabaseUrlConfig; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; @@ -68,9 +68,9 @@ CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:c @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") CredentialsSource dbCredentialsSource( DatabaseClient databaseClient, - DatabaseUrlProperties databaseProperties, + DatabaseUrlConfig databaseUrlConfig, DatabaseUrlBuilder databaseUrlBuilder) { - return new DatabaseCredentialsSource(databaseClient, databaseUrlBuilder.build(databaseProperties)); + return new DatabaseCredentialsSource(databaseClient, databaseUrlBuilder.build(databaseUrlConfig)); } @Bean diff --git a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java b/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java deleted file mode 100644 index fbe39071..00000000 --- a/model/src/main/java/javasabr/mqtt/model/DatabaseUrlBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package javasabr.mqtt.model; - -import javasabr.mqtt.model.database.DatabaseUrlProperties; - -public interface DatabaseUrlBuilder { - String build(DatabaseUrlProperties databaseProperties); -} diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java similarity index 77% rename from model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java rename to model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java index a60ccb32..c3dbd58d 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java @@ -2,7 +2,7 @@ import java.time.Duration; -public interface DatabasePoolProperties { +public interface DatabasePoolConfig { Duration maxIdleTime(); diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java similarity index 67% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java rename to model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java index 3d93c23f..9d76739e 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java @@ -1,6 +1,6 @@ package javasabr.mqtt.model.database; -public interface DatabaseTimeoutsProperties { +public interface DatabaseTimeoutsConfig { String lockTimeout(); diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java new file mode 100644 index 00000000..178906cc --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.model.database; + +public interface DatabaseUrlBuilder { + String build(DatabaseUrlConfig databaseUrlConfig); +} diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java similarity index 61% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java rename to model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java index b97e6944..8fb379c6 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java @@ -1,8 +1,6 @@ package javasabr.mqtt.model.database; -import java.time.Duration; - -public interface DatabaseUrlProperties { +public interface DatabaseUrlConfig { String driver(); String host(); diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java similarity index 88% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java rename to model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java index 32627853..0d2ee1a7 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java @@ -3,7 +3,7 @@ import java.util.Map; import javasabr.mqtt.model.Credentials; -public interface DatabaseUsersProperties { +public interface DatabaseUsersConfig { Credentials ANONYMOUS_CREDENTIAL = new Credentials("", ""); From 8d70b2baa893d5792b3971ae6c355671ee5f0b98 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:26:18 +0100 Subject: [PATCH 032/107] [broker-141] Add constraints to user_credentials table --- .../db/migration/V1__Create_User_Credentials_Table.sql | 9 ++++++--- .../mqtt/auth/provider/BasicAuthenticationProvider.java | 9 ++------- .../mqtt/auth/api/InMemoryCredentialsSource.java | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql index 972a7b9d..1aa1b883 100644 --- a/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql +++ b/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql @@ -1,4 +1,7 @@ -CREATE TABLE user_credentials ( - username VARCHAR(255), - password VARBINARY(255) +CREATE TABLE user_credentials +( + username VARCHAR(255) NOT NULL PRIMARY KEY, + password VARBINARY(255) NOT NULL, + CONSTRAINT username_not_empty CHECK (octet_length(username) > 0), + CONSTRAINT password_not_empty CHECK (octet_length(password) > 0) ); diff --git a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 764c3c4f..c14e45e7 100644 --- a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -5,7 +5,6 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; @RequiredArgsConstructor @@ -20,12 +19,8 @@ public String getName() { } @Override - public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { - if (username == null) { - return Mono.just(false); - } else { - return credentialsSource.isCredentialsExists(username, password); - } + public Mono authenticate(String username, byte[] password, byte[] data) { + return credentialsSource.isCredentialsExists(username, password); } @Override diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java index b5a797bd..42982eaa 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java @@ -16,7 +16,7 @@ public abstract class InMemoryCredentialsSource implements CredentialsSource { private final LockableRefToRefDictionary credentials = DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); - protected void reset(RefToRefDictionary otherCredentials) { + private void reset(RefToRefDictionary otherCredentials) { long stamp = credentials.writeLock(); try { credentials.clear(); @@ -26,7 +26,7 @@ protected void reset(RefToRefDictionary otherCredentials) { } } - void put(String user, byte[] pass) { + private void put(String user, byte[] pass) { long stamp = credentials.writeLock(); try { credentials.put(user, pass); From 652b88c016ff79916cf19c97e349f45bc705cdf5 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:19:04 +0100 Subject: [PATCH 033/107] [broker-141] Rename auth-provider-basic to authentication-provider-basic --- application/build.gradle | 2 +- .../build.gradle | 0 .../mqtt/auth/provider/BasicAuthenticationProvider.java | 0 authentication-service/build.gradle | 2 +- settings.gradle | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename {auth-provider-basic => authentication-provider-basic}/build.gradle (100%) rename {auth-provider-basic => authentication-provider-basic}/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java (100%) diff --git a/application/build.gradle b/application/build.gradle index ac5703c2..2ea6b133 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -11,7 +11,7 @@ dependencies { implementation projects.coreService implementation projects.aclService implementation projects.aclGroovyDsl - implementation projects.authProviderBasic + implementation projects.authenticationProviderBasic implementation projects.authenticationApi implementation projects.authenticationService implementation libs.rlib.logger.slf4j diff --git a/auth-provider-basic/build.gradle b/authentication-provider-basic/build.gradle similarity index 100% rename from auth-provider-basic/build.gradle rename to authentication-provider-basic/build.gradle diff --git a/auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java similarity index 100% rename from auth-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java rename to authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index c64a7163..a48d6d81 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -14,7 +14,7 @@ dependencies { compileOnlyApi projects.credentialsSourceDb compileOnlyApi projects.credentialsSourceFile - compileOnlyApi projects.authProviderBasic + compileOnlyApi projects.authenticationProviderBasic testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/settings.gradle b/settings.gradle index 332380d8..a6b144ce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,6 @@ include(":acl-engine") include(":acl-service") include(":authentication-api") include(":authentication-service") -include(":auth-provider-basic") +include(":authentication-provider-basic") include(":credentials-source-file") include(":credentials-source-db") From 7d6f7fa81cc84f237605ace64eb0b97a2409eaee Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:23:33 +0100 Subject: [PATCH 034/107] [broker-141] Move exception to separate package --- .../auth/api/{ => exception}/AuthenticationConfigException.java | 2 +- .../auth/api/{ => exception}/CredentialsSourceException.java | 2 +- .../auth/service/config/BasicAuthenticationSpringConfig.java | 2 +- .../mqtt/auth/credentials/source/FileCredentialsSource.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => exception}/AuthenticationConfigException.java (78%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => exception}/CredentialsSourceException.java (85%) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/AuthenticationConfigException.java similarity index 78% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/AuthenticationConfigException.java index 8eec5042..da2aefe2 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationConfigException.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/AuthenticationConfigException.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.exception; public class AuthenticationConfigException extends RuntimeException { diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/CredentialsSourceException.java similarity index 85% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/CredentialsSourceException.java index 3e93d494..4bd41438 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceException.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/exception/CredentialsSourceException.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.exception; public class CredentialsSourceException extends RuntimeException { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index c22619e5..c7832843 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -4,7 +4,7 @@ import java.net.URI; import java.util.List; import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; -import javasabr.mqtt.auth.api.AuthenticationConfigException; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index fbb558e0..b75498b9 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -4,7 +4,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import javasabr.mqtt.auth.api.CredentialsSourceException; +import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import javasabr.mqtt.auth.api.InMemoryCredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; From 40478428cd6db084a63ebfb1deace6ce376e3bfd Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:41:58 +0100 Subject: [PATCH 035/107] [broker-141] Remove ConnectToAuthRequestConverter --- .../converter/ConnectToAuthRequestConverter.java | 16 ---------------- .../message/converter/MessageConverter.java | 6 ------ .../impl/ConnectInMqttInMessageHandler.java | 11 ++++++----- 3 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java delete mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java b/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java deleted file mode 100644 index 9d43c22f..00000000 --- a/core-service/src/main/java/javasabr/mqtt/service/message/converter/ConnectToAuthRequestConverter.java +++ /dev/null @@ -1,16 +0,0 @@ -package javasabr.mqtt.service.message.converter; - -import javasabr.mqtt.auth.api.AuthRequest; -import javasabr.mqtt.network.message.in.ConnectMqttInMessage; - -public class ConnectToAuthRequestConverter implements MessageConverter { - - @Override - public AuthRequest convert(ConnectMqttInMessage message) { - return new AuthRequest( - message.username(), - message.password(), - message.authenticationMethod(), - message.authenticationData()); - } -} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java b/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java deleted file mode 100644 index d1e14cee..00000000 --- a/core-service/src/main/java/javasabr/mqtt/service/message/converter/MessageConverter.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.service.message.converter; - -public interface MessageConverter { - - Out convert(In message); -} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 2a5ccff8..8885836c 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,12 +11,12 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; +import javasabr.mqtt.auth.api.AuthRequest; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; -import javasabr.mqtt.auth.api.AuthRequest; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -28,7 +28,6 @@ import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; -import javasabr.mqtt.service.message.converter.ConnectToAuthRequestConverter; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; @@ -45,7 +44,6 @@ public class ConnectInMqttInMessageHandler AuthenticationService authenticationService; MqttSessionService sessionService; SubscriptionService subscriptionService; - ConnectToAuthRequestConverter connectToAuthRequestConverter; public ConnectInMqttInMessageHandler( ClientIdRegistry clientIdRegistry, @@ -58,7 +56,6 @@ public ConnectInMqttInMessageHandler( this.authenticationService = authenticationService; this.sessionService = sessionService; this.subscriptionService = subscriptionService; - this.connectToAuthRequestConverter = new ConnectToAuthRequestConverter(); } @Override @@ -77,7 +74,11 @@ protected void processValidMessage( ExternalNetworkMqttUser user, ConnectMqttInMessage message) { resolveClientConnectionConfig(user, message); - AuthRequest authRequest = connectToAuthRequestConverter.convert(message); + AuthRequest authRequest = new AuthRequest( + message.username(), + message.password(), + message.authenticationMethod(), + message.authenticationData()); authenticationService .authenticate(authRequest) .flatMap(ifTrue( From 1dc642737314d097cb37080e14b26c2ebf94196c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:02:46 +0100 Subject: [PATCH 036/107] [broker-141] Move db-specific configs to credentials-source-db --- .../config/DatabaseConnectionProperties.java | 10 +++++----- .../application/config/DatabaseSpringConfig.java | 12 ++++++------ .../config/DatabaseTestSpringConfig.groovy | 4 ++-- .../config/BasicAuthenticationSpringConfig.java | 4 ++-- .../auth/credentials/source/config}/Credentials.java | 2 +- .../source/config}/DatabasePoolConfig.java | 2 +- .../source/config}/DatabaseTimeoutsConfig.java | 2 +- .../source/config}/DatabaseUrlBuilder.java | 2 +- .../source/config}/DatabaseUrlConfig.java | 2 +- .../source/config}/DatabaseUsersConfig.java | 3 +-- 10 files changed, 21 insertions(+), 22 deletions(-) rename {model/src/main/java/javasabr/mqtt/model => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/Credentials.java (54%) rename {model/src/main/java/javasabr/mqtt/model/database => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/DatabasePoolConfig.java (72%) rename {model/src/main/java/javasabr/mqtt/model/database => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/DatabaseTimeoutsConfig.java (65%) rename {model/src/main/java/javasabr/mqtt/model/database => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/DatabaseUrlBuilder.java (63%) rename {model/src/main/java/javasabr/mqtt/model/database => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/DatabaseUrlConfig.java (67%) rename {model/src/main/java/javasabr/mqtt/model/database => credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config}/DatabaseUsersConfig.java (78%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java index 6a6631fa..cf31a55a 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java @@ -2,11 +2,11 @@ import java.time.Duration; import java.util.Map; -import javasabr.mqtt.model.Credentials; -import javasabr.mqtt.model.database.DatabasePoolConfig; -import javasabr.mqtt.model.database.DatabaseTimeoutsConfig; -import javasabr.mqtt.model.database.DatabaseUrlConfig; -import javasabr.mqtt.model.database.DatabaseUsersConfig; +import javasabr.mqtt.auth.credentials.source.config.Credentials; +import javasabr.mqtt.auth.credentials.source.config.DatabasePoolConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseTimeoutsConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUsersConfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "persistence.database") diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java index a4101544..2f019511 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java @@ -14,12 +14,12 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.model.Credentials; -import javasabr.mqtt.model.database.DatabaseUrlBuilder; -import javasabr.mqtt.model.database.DatabasePoolConfig; -import javasabr.mqtt.model.database.DatabaseTimeoutsConfig; -import javasabr.mqtt.model.database.DatabaseUrlConfig; -import javasabr.mqtt.model.database.DatabaseUsersConfig; +import javasabr.mqtt.auth.credentials.source.config.Credentials; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder; +import javasabr.mqtt.auth.credentials.source.config.DatabasePoolConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseTimeoutsConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUsersConfig; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy index 087fa90b..7b2a3e62 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy @@ -3,8 +3,8 @@ package javasabr.mqtt.broker.application.config import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option -import javasabr.mqtt.model.Credentials -import javasabr.mqtt.model.database.DatabaseUrlBuilder +import javasabr.mqtt.auth.credentials.source.config.Credentials +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index c7832843..e0018cee 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -12,8 +12,8 @@ import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.model.database.DatabaseUrlBuilder; -import javasabr.mqtt.model.database.DatabaseUrlConfig; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder; +import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; diff --git a/model/src/main/java/javasabr/mqtt/model/Credentials.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java similarity index 54% rename from model/src/main/java/javasabr/mqtt/model/Credentials.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java index d55cbb7c..30fd4eb7 100644 --- a/model/src/main/java/javasabr/mqtt/model/Credentials.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.model; +package javasabr.mqtt.auth.credentials.source.config; public record Credentials(String username, String password) {} diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java similarity index 72% rename from model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java index c3dbd58d..e78f4376 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabasePoolConfig.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.database; +package javasabr.mqtt.auth.credentials.source.config; import java.time.Duration; diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java similarity index 65% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java index 9d76739e..ceebc5a8 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseTimeoutsConfig.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.database; +package javasabr.mqtt.auth.credentials.source.config; public interface DatabaseTimeoutsConfig { diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java similarity index 63% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java index 178906cc..d6163535 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlBuilder.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.database; +package javasabr.mqtt.auth.credentials.source.config; public interface DatabaseUrlBuilder { String build(DatabaseUrlConfig databaseUrlConfig); diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java similarity index 67% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java index 8fb379c6..9eb26b9b 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUrlConfig.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.model.database; +package javasabr.mqtt.auth.credentials.source.config; public interface DatabaseUrlConfig { String driver(); diff --git a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java similarity index 78% rename from model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java index 0d2ee1a7..bfdd9c9a 100644 --- a/model/src/main/java/javasabr/mqtt/model/database/DatabaseUsersConfig.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java @@ -1,7 +1,6 @@ -package javasabr.mqtt.model.database; +package javasabr.mqtt.auth.credentials.source.config; import java.util.Map; -import javasabr.mqtt.model.Credentials; public interface DatabaseUsersConfig { From da833191f1875931cc39d36abc7522b77ebbc5f8 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:12:31 +0100 Subject: [PATCH 037/107] [broker-141] Remove redundant @Nullable annotation --- .../mqtt/auth/api/AnonymousAuthenticationProvider.java | 2 +- .../java/javasabr/mqtt/auth/api/AuthenticationProvider.java | 3 +-- .../main/java/javasabr/mqtt/auth/api/CredentialsSource.java | 2 +- .../javasabr/mqtt/auth/api/InMemoryCredentialsSource.java | 4 ++-- .../mqtt/auth/service/DefaultAuthenticationService.java | 3 +-- .../auth/credentials/source/DatabaseCredentialsSource.java | 6 +++--- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java index 2acf5c9f..2ecdf825 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java @@ -11,7 +11,7 @@ public String getName() { } @Override - public Mono authenticate(@Nullable String username, byte[] password, byte[] data) { + public Mono authenticate(String username, byte[] password, byte[] data) { return Mono.just(StringUtils.isEmpty(username)); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index d87447a9..371534d6 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -1,11 +1,10 @@ package javasabr.mqtt.auth.api; -import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; public interface AuthenticationProvider { String getName(); - Mono authenticate(@Nullable String username, byte[] password, byte[] data); + Mono authenticate(String username, byte[] password, byte[] data); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java index 3f0cf2ca..0f97edd8 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -6,5 +6,5 @@ public interface CredentialsSource { String getName(); - Mono isCredentialsExists(String user, byte[] pass); + Mono isCredentialsExists(String userName, byte[] password); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java index 42982eaa..93f67ad8 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java @@ -47,7 +47,7 @@ protected void reset(InputStream inStream) throws IOException { } @Override - public Mono isCredentialsExists(String user, byte[] pass) { - return Mono.just(Arrays.equals(pass, credentials.get(user))); + public Mono isCredentialsExists(String userName, byte[] password) { + return Mono.just(Arrays.equals(password, credentials.get(userName))); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 3e663d6d..77e92cd3 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -26,9 +26,8 @@ public DefaultAuthenticationService( @Override public Mono authenticate(AuthRequest request) { - String username = request.username(); return providers.getOrDefault(request.authenticationMethod(), defaultProvider) - .authenticate(username, request.password(), request.authenticationData()); + .authenticate(request.username(), request.password(), request.authenticationData()); } private static String buildServiceDescription( diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index cb1ee97e..2fcc9e0f 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -28,14 +28,14 @@ public String getName() { } @Override - public Mono isCredentialsExists(String username, byte[] requestedPassword) { + public Mono isCredentialsExists(String userName, byte[] password) { return databaseClient .sql(CREDENTIALS_QUERY) - .bind("$1", username) + .bind("$1", userName) .map(row -> row.get("password", byte[].class)) .all() .singleOrEmpty() - .map(existingPassword -> Arrays.equals(existingPassword, requestedPassword)) + .map(existingPassword -> Arrays.equals(existingPassword, password)) .defaultIfEmpty(false); } From 364be20fdac7e37fba40a7aa05f7bd3ba6aa62c7 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:50:16 +0100 Subject: [PATCH 038/107] [broker-141] Move AuthenticationServiceConfig to authentication-service module --- application/build.gradle | 11 - .../config/MqttBrokerSpringConfig.java | 1 + .../src/main/resources/application.properties | 2 +- .../application/MqttClientFactory.groovy | 2 +- .../config/MqttBrokerTestConfig.groovy | 10 +- .../service/AuthenticationServiceTest.groovy | 219 ------------------ .../service/SubscriptionServiceTest.groovy | 4 +- .../resources/application-test.properties | 2 +- authentication-service/build.gradle | 14 ++ .../config/AuthenticationProperties.java | 21 ++ .../BasicAuthenticationSpringConfig.java | 37 +-- .../auth/service}/config/Credentials.java | 2 +- .../config/DatabaseConnectionProperties.java | 7 +- .../service}/config/DatabasePoolConfig.java | 2 +- .../service}/config/DatabaseSpringConfig.java | 34 +-- .../config/DatabaseTimeoutsConfig.java | 2 +- .../service}/config/DatabaseUrlBuilder.java | 2 +- .../service}/config/DatabaseUrlConfig.java | 2 +- .../service}/config/DatabaseUsersConfig.java | 2 +- .../ConditionalOnAnonymousProvider.java | 14 ++ .../ConditionalOnAuthenticationProvider.java | 12 + ...ditionalOnBasicAuthenticationProvider.java | 14 ++ .../ConditionalOnCredentialsSource.java | 15 ++ ...onditionalOnDatabaseCredentialsSource.java | 14 ++ .../ConditionalOnFileCredentialsSource.java | 14 ++ .../condition/AnonymousProviderCondition.java | 24 ++ .../AuthenticationProviderCondition.java | 31 +++ .../condition/CredentialsSourceCondition.java | 31 +++ .../auth/service/config/package-info.java | 4 + .../V1__Create_User_Credentials_Table.sql | 0 .../service/AuthenticationProviderTest.groovy | 137 +++++++++++ .../DatabaseAuthenticationServiceTest.groovy | 34 +++ .../FileAuthenticationServiceTest.groovy | 34 +++ .../service/IntegrationSpecification.groovy | 17 ++ .../resources/application-test.properties | 12 + .../src/test/resources/credentials/test | 2 + .../db/migration/V1.1__Insert_Test_User.sql | 0 .../service}/DatabaseTestSpringConfig.groovy | 8 +- 38 files changed, 507 insertions(+), 286 deletions(-) delete mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/Credentials.java (54%) rename {application/src/main/java/javasabr/mqtt/broker/application => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseConnectionProperties.java (58%) rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabasePoolConfig.java (72%) rename {application/src/main/java/javasabr/mqtt/broker/application => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseSpringConfig.java (84%) rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseTimeoutsConfig.java (65%) rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseUrlBuilder.java (63%) rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseUrlConfig.java (67%) rename {credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source => authentication-service/src/main/java/javasabr/mqtt/auth/service}/config/DatabaseUsersConfig.java (83%) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/package-info.java rename {application => authentication-service}/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql (100%) create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy create mode 100644 authentication-service/src/test/resources/application-test.properties create mode 100644 authentication-service/src/test/resources/credentials/test rename {application => authentication-service}/src/test/resources/db/migration/V1.1__Insert_Test_User.sql (100%) rename {application/src/test/groovy/javasabr/mqtt/broker/application/config => authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service}/DatabaseTestSpringConfig.groovy (80%) diff --git a/application/build.gradle b/application/build.gradle index 2ea6b133..6a053148 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -20,17 +20,6 @@ dependencies { testImplementation projects.testSupport testImplementation testFixtures(projects.network) - testImplementation projects.credentialsSourceFile - testImplementation projects.credentialsSourceDb - - // database support - implementation libs.r2dbc.spi - implementation libs.r2dbc.pool - implementation libs.r2dbc.postgresql - implementation libs.jdbc.postgres - implementation libs.flyway.core - implementation libs.flyway.postgresql - testImplementation libs.r2dbc.h2 } tasks.withType(GroovyCompile).configureEach { diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 0b98f26e..2710042e 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -6,6 +6,7 @@ import javasabr.mqtt.acl.service.conifg.GroovyDslBasedAclServiceSpringConfig; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig; +import javasabr.mqtt.auth.service.config.DatabaseSpringConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index d5a70fdc..293f8cfc 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -1,2 +1,2 @@ -authentication.allow.anonymous=false +authentication.allow-anonymous=false credentials.source.file.name=credentials diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy index 13372460..6e315bc6 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy @@ -6,7 +6,7 @@ import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient import java.util.concurrent.atomic.AtomicInteger -class MqttClientFactory { +public class MqttClientFactory { private static final idGenerator = new AtomicInteger(1) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy index d685b841..89470e89 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy @@ -1,7 +1,11 @@ //file:noinspection SpringJavaInjectionPointsAutowiringInspection package javasabr.mqtt.broker.application.config + import javasabr.mqtt.network.MqttConnection + +//import javasabr.mqtt.broker.application.service.DatabaseTestSpringConfig + import javasabr.mqtt.service.ConnectionService import javasabr.rlib.network.server.ServerNetwork import org.springframework.boot.context.event.ApplicationStartedEvent @@ -14,7 +18,7 @@ import java.util.concurrent.ThreadLocalRandom @Import([ MqttBrokerSpringConfig, - DatabaseTestSpringConfig +// DatabaseTestSpringConfig ]) @Configuration(proxyBeanMethods = false) class MqttBrokerTestConfig { @@ -27,7 +31,7 @@ class MqttBrokerTestConfig { try { externalNetwork.start(address) return address; - } catch (RuntimeException e) { + } catch (RuntimeException ignored) { } } throw new RuntimeException() @@ -43,6 +47,6 @@ class MqttBrokerTestConfig { ConnectionService connectionService, InetSocketAddress externalNetworkAddress) { externalNetwork.onAccept(connectionService::processAcceptedConnection); - return null; + return null } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy deleted file mode 100644 index fa552cbc..00000000 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ /dev/null @@ -1,219 +0,0 @@ -package javasabr.mqtt.broker.application.service - -import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient -import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException -import com.hivemq.client.mqtt.mqtt3.message.auth.Mqtt3SimpleAuth -import com.hivemq.client.mqtt.mqtt3.message.connect.Mqtt3Connect -import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAck -import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode -import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException -import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth -import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import javasabr.mqtt.broker.application.ContextRunnerSpecification -import javasabr.mqtt.broker.application.MqttClientFactory -import javasabr.mqtt.broker.application.config.MqttBrokerTestConfig - -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletionException - -class AuthenticationServiceTest extends ContextRunnerSpecification { - - def setup() { - createContextRunner(MqttBrokerTestConfig, "application-test.properties") - } - - def "should not be able to connect with wrong password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - def wrongPassword = "wrong-password".getBytes(StandardCharsets.UTF_8) - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> - // when - def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() - // then - assert exception instanceof CompletionException - assert exception.cause instanceof Mqtt3ConnAckException - with(exception.cause as Mqtt3ConnAckException) { - mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD - } - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } - - def "should not be able to connect with wrong password using mqtt 5 client"() { - given: - def existingUsername = "user" - def wrongPassword = "wrong-password".getBytes(StandardCharsets.UTF_8) - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(wrongPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> - // when - def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() - // then - assert exception instanceof CompletionException - assert exception.cause instanceof Mqtt5ConnAckException - with(exception.cause as Mqtt5ConnAckException) { - mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } - - def "should be able to connect with correct password using mqtt 3.1.1 client"() { - given: - def existingUsername = "user" - byte[] correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) - and: - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> - // when - Mqtt3ConnAck ack = subscriber.connect(connectMessage).join() - // then - assert ack instanceof Mqtt3ConnAck - assert ack.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - subscriber.disconnect().join() - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } - - def "should be able to connect with correct password using mqtt 5 client"() { - given: - def existingUsername = "user" - byte[] correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) - and: - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(existingUsername) - .password(correctPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> - // when - Mqtt5ConnAck ack = subscriber.connect(connectMessage).join() - // then - assert ack instanceof Mqtt5ConnAck - assert ack.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - subscriber.disconnect().join() - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } - - def "should not be able to connect without username and with correct password using mqtt 5 client"() { - given: - def blankUsername = "" - def correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) - and: - def connectMessage = Mqtt5Connect.builder() - .simpleAuth(Mqtt5SimpleAuth.builder() - .username(blankUsername) - .password(correctPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt5Client) { Mqtt5AsyncClient subscriber -> - // when - def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() - // then - assert exception instanceof CompletionException - assert exception.cause instanceof Mqtt5ConnAckException - with(exception.cause as Mqtt5ConnAckException) { - mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } - - def "should not be able to connect without username and with correct password using mqtt 3.1.1 client"() { - given: - def emptyUserName = "" - def correctPassword = "correct-password".getBytes(StandardCharsets.UTF_8) - and: - def connectMessage = Mqtt3Connect.builder() - .simpleAuth(Mqtt3SimpleAuth.builder() - .username(emptyUserName) - .password(correctPassword) - .build()) - .build() - String[] properties = new String[]{ - "authentication.allow.anonymous=false", - "authentication.provider=$provider", - "authentication.credentials.source=$source" - } - expect: - runContextWithProperties(properties, MqttClientFactory.&buildMqtt311Client) { Mqtt3AsyncClient subscriber -> - // when - def exception = { try { subscriber.connect(connectMessage).join() } catch(e) { return e } }() - // then - assert exception instanceof CompletionException - assert exception.cause instanceof Mqtt3ConnAckException - with(exception.cause as Mqtt3ConnAckException) { - mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD - } - } - where: - source | provider - "file" | "basic" - "database" | "basic" - } -} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscriptionServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscriptionServiceTest.groovy index 7fa776e7..20d096c9 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscriptionServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscriptionServiceTest.groovy @@ -38,7 +38,7 @@ class SubscriptionServiceTest extends IntegrationSpecification { .findSubscribers(topicName) then: "should find the subscriber" subscribers.size() == 1 - subscribers.get(0).user() instanceof NetworkMqttUser + (subscribers.get(0).user() instanceof NetworkMqttUser) when: def matchedSubscriber = subscribers.get(0) def subscription = matchedSubscriber.subscription() @@ -61,7 +61,7 @@ class SubscriptionServiceTest extends IntegrationSpecification { .findSubscribers(topicName) then: "should find the reconnected subscriber" subscribers3.size() == 1 - subscribers3.get(0).user() instanceof NetworkMqttUser + (subscribers3.get(0).user() instanceof NetworkMqttUser) when: matchedSubscriber = subscribers3.get(0) subscription = matchedSubscriber.subscription() diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 3d68da0e..b6d0b1b5 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,4 +1,4 @@ -authentication.allow.anonymous=true +authentication.allow-anonymous=true credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index a48d6d81..d1064719 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -16,6 +16,20 @@ dependencies { compileOnlyApi projects.credentialsSourceFile compileOnlyApi projects.authenticationProviderBasic + testImplementation projects.credentialsSourceDb + testImplementation projects.credentialsSourceFile + testImplementation projects.authenticationProviderBasic + testImplementation libs.r2dbc.h2 + testImplementation projects.testSupport testFixturesApi projects.testSupport + +// database support + implementation libs.r2dbc.spi + implementation libs.r2dbc.pool + implementation libs.r2dbc.postgresql + implementation libs.jdbc.postgres + implementation libs.flyway.core + implementation libs.flyway.postgresql + api libs.spring.r2dbc } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java new file mode 100644 index 00000000..76ca7ebf --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java @@ -0,0 +1,21 @@ +package javasabr.mqtt.auth.service.config; + +import java.util.List; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; +import org.jspecify.annotations.Nullable; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@ConfigurationProperties(prefix = "authentication") +public record AuthenticationProperties( + boolean allowAnonymous, + @Nullable String defaultProvider, + @Nullable List providers, + @Nullable List credentialsSources) { + + public AuthenticationProperties { + if (!allowAnonymous && (providers == null || providers.isEmpty())) { + throw new AuthenticationConfigException("Authenticator providers are not configured"); + } + } +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java index e0018cee..60c61fd8 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java @@ -4,33 +4,40 @@ import java.net.URI; import java.util.List; import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import javasabr.mqtt.auth.service.config.DatabaseUrlBuilder; +import javasabr.mqtt.auth.service.config.DatabaseUrlConfig; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAnonymousProvider; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnBasicAuthenticationProvider; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnFileCredentialsSource; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.r2dbc.core.DatabaseClient; @CustomLog @Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties({ + AuthenticationProperties.class +}) public class BasicAuthenticationSpringConfig { @Bean AuthenticationService authenticationService( List authenticationProviders, - @Value("${authentication.provider.default:#{null}}") String defaultProviderName) { + @Value("${authentication.default-provider:#{null}}") @Nullable String defaultProviderName) { log.info("Initializing AuthenticationService..."); if (authenticationProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); @@ -49,14 +56,10 @@ AuthenticationService authenticationService( return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider); } - @Bean - DatabaseClient databaseClient(ConnectionFactory connectionFactory) { - return DatabaseClient.create(connectionFactory); - } + @Bean - @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "file") - @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") + @ConditionalOnFileCredentialsSource CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName); fileCredentialsSource.init(); @@ -64,8 +67,7 @@ CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:c } @Bean - @ConditionalOnProperty(name = "authentication.credentials.source", havingValue = "database") - @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") + @ConditionalOnDatabaseCredentialsSource CredentialsSource dbCredentialsSource( DatabaseClient databaseClient, DatabaseUrlConfig databaseUrlConfig, @@ -74,14 +76,13 @@ CredentialsSource dbCredentialsSource( } @Bean - @ConditionalOnProperty(name = "authentication.provider", havingValue = "basic") - @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") - AuthenticationProvider passwordBasedAuthenticationProvider(CredentialsSource credentialsSource) { + @ConditionalOnBasicAuthenticationProvider + AuthenticationProvider basicAuthenticationProvider(CredentialsSource credentialsSource) { return new BasicAuthenticationProvider(credentialsSource); } @Bean - @ConditionalOnProperty(name = "authentication.allow.anonymous", havingValue = "true") + @ConditionalOnAnonymousProvider AuthenticationProvider anonymousAuthenticationProvider() { return new AnonymousAuthenticationProvider(); } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java similarity index 54% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java index 30fd4eb7..eaadfdba 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/Credentials.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; public record Credentials(String username, String password) {} diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java similarity index 58% rename from application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java index cf31a55a..27ff7a1b 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseConnectionProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java @@ -1,12 +1,7 @@ -package javasabr.mqtt.broker.application.config; +package javasabr.mqtt.auth.service.config; import java.time.Duration; import java.util.Map; -import javasabr.mqtt.auth.credentials.source.config.Credentials; -import javasabr.mqtt.auth.credentials.source.config.DatabasePoolConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseTimeoutsConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUsersConfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "persistence.database") diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java similarity index 72% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java index e78f4376..e9b7a80d 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabasePoolConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; import java.time.Duration; diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java similarity index 84% rename from application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 2f019511..85e7b113 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.broker.application.config; +package javasabr.mqtt.auth.service.config; import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; @@ -14,21 +14,20 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.credentials.source.config.Credentials; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder; -import javasabr.mqtt.auth.credentials.source.config.DatabasePoolConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseTimeoutsConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlConfig; -import javasabr.mqtt.auth.credentials.source.config.DatabaseUsersConfig; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(DatabaseConnectionProperties.class) +@EnableConfigurationProperties({ + DatabaseConnectionProperties.class +}) +@ConditionalOnDatabaseCredentialsSource public class DatabaseSpringConfig { @Bean @@ -50,6 +49,17 @@ ConnectionFactoryOptions connectionFactoryOptions( .build(); } + @Bean + DatabaseUrlBuilder databaseUrlBuilder() { + return db -> "jdbc:%s://%s:%s/%s".formatted(db.driver(), db.host(), db.port(), db.name()); + } + + @Bean + @ConditionalOnDatabaseCredentialsSource + DatabaseClient databaseClient(ConnectionFactory connectionFactory) { + return DatabaseClient.create(connectionFactory); + } + @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( @@ -70,13 +80,7 @@ Flyway flyway( DatabaseUrlConfig databaseUrlConfig, @Qualifier("adminCredentials") Credentials credentials) { return Flyway.configure() - .dataSource(databaseUrlBuilder.build(databaseUrlConfig), credentials.username(), credentials.password()) - .load(); - } - - @Bean - DatabaseUrlBuilder databaseUrlBuilder() { - return db -> "jdbc:%s://%s:%s/%s".formatted(db.driver(), db.host(), db.port(), db.name()); + .dataSource(databaseUrlBuilder.build(databaseUrlConfig), credentials.username(), credentials.password()).load(); } @Bean diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java similarity index 65% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java index ceebc5a8..e0781a5f 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseTimeoutsConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; public interface DatabaseTimeoutsConfig { diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java similarity index 63% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java index d6163535..3165b062 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlBuilder.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; public interface DatabaseUrlBuilder { String build(DatabaseUrlConfig databaseUrlConfig); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java similarity index 67% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java index 9eb26b9b..5792fa86 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUrlConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; public interface DatabaseUrlConfig { String driver(); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java similarity index 83% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java index bfdd9c9a..b494467f 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseUsersConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.credentials.source.config; +package javasabr.mqtt.auth.service.config; import java.util.Map; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java new file mode 100644 index 00000000..719b3067 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javasabr.mqtt.auth.service.config.condition.AnonymousProviderCondition; +import org.springframework.context.annotation.Conditional; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Conditional(AnonymousProviderCondition.class) +public @interface ConditionalOnAnonymousProvider { +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java new file mode 100644 index 00000000..71112ccd --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import javasabr.mqtt.auth.service.config.condition.AuthenticationProviderCondition; +import org.springframework.context.annotation.Conditional; +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Conditional(AuthenticationProviderCondition.class) +public @interface ConditionalOnAuthenticationProvider { + String value(); +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java new file mode 100644 index 00000000..708faaee --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") +@ConditionalOnAuthenticationProvider("basic") +public @interface ConditionalOnBasicAuthenticationProvider { +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java new file mode 100644 index 00000000..955f0233 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java @@ -0,0 +1,15 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javasabr.mqtt.auth.service.config.condition.CredentialsSourceCondition; +import org.springframework.context.annotation.Conditional; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Conditional(CredentialsSourceCondition.class) +public @interface ConditionalOnCredentialsSource { + String value(); +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java new file mode 100644 index 00000000..9e7ba21c --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnCredentialsSource("database") +@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") +public @interface ConditionalOnDatabaseCredentialsSource { +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java new file mode 100644 index 00000000..6890dff3 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.auth.service.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnCredentialsSource("file") +@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") +public @interface ConditionalOnFileCredentialsSource { +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java new file mode 100644 index 00000000..36c8972a --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java @@ -0,0 +1,24 @@ +package javasabr.mqtt.auth.service.config.condition; + +import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class AnonymousProviderCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + return Binder.get(context.getEnvironment()) + .bind("authentication", AuthenticationProperties.class) + .map(authProps -> { + if (authProps.allowAnonymous()) { + return ConditionOutcome.match("Anonymous connections allowed"); + } else { + return ConditionOutcome.noMatch("Anonymous connections denied"); + } + }) + .orElse(ConditionOutcome.noMatch("Authentication providers are not configured")); + } +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java new file mode 100644 index 00000000..b32ef6e5 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java @@ -0,0 +1,31 @@ +package javasabr.mqtt.auth.service.config.condition; + +import java.util.List; +import java.util.Map; +import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAuthenticationProvider; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class AuthenticationProviderCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnAuthenticationProvider.class.getName()); + String requiredProvider = attributes.getOrDefault("value", "").toString(); + + return Binder.get(context.getEnvironment()) + .bind("authentication", AuthenticationProperties.class) + .map(authProps -> { + List providers = authProps.providers(); + if (providers != null && providers.contains(requiredProvider)) { + return ConditionOutcome.match("Provider '" + requiredProvider + "' found in MqttProperties"); + } else { + return ConditionOutcome.noMatch("Provider '" + requiredProvider + "' not active"); + } + }) + .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + } +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java new file mode 100644 index 00000000..e5579deb --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java @@ -0,0 +1,31 @@ +package javasabr.mqtt.auth.service.config.condition; + +import java.util.List; +import java.util.Map; +import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnCredentialsSource; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class CredentialsSourceCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnCredentialsSource.class.getName()); + String requiredSource = attributes.getOrDefault("value", "").toString(); + + return Binder.get(context.getEnvironment()) + .bind("authentication", AuthenticationProperties.class) + .map(authProps -> { + List sources = authProps.credentialsSources(); + if (sources != null && sources.contains(requiredSource)) { + return ConditionOutcome.match("Source '" + requiredSource + "' is enabled"); + } + return ConditionOutcome.noMatch("Source '" + requiredSource + "' not in config"); + }) + .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + } +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/package-info.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/package-info.java new file mode 100644 index 00000000..d4e50a97 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.auth.service.config; + +import org.jspecify.annotations.NullMarked; diff --git a/application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql similarity index 100% rename from application/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql rename to authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy new file mode 100644 index 00000000..c5de0c71 --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -0,0 +1,137 @@ +//file:noinspection SpringBootApplicationProperties +package javasabr.mqtt.broker.application.service + +import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider +import javasabr.mqtt.auth.api.AuthenticationProvider +import javasabr.mqtt.auth.api.CredentialsSource +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource +import javasabr.mqtt.auth.credentials.source.FileCredentialsSource +import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig +import javasabr.mqtt.auth.service.config.DatabaseSpringConfig +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.env.PropertiesPropertySourceLoader +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.core.env.PropertySource +import org.springframework.core.io.ClassPathResource +import org.springframework.test.context.TestPropertySource +import spock.lang.Ignore +import spock.lang.Specification + +class AuthenticationProviderTest extends IntegrationSpecification { + + @Autowired + List authenticationProviders + + @TestPropertySource(properties = [ + "authentication.allow-anonymous=false", + "authentication.providers[0]=basic", + "authentication.credentials-sources[0]=database" + ]) + static class DatabaseCredentialsSourceTest extends AuthenticationProviderTest { + @Autowired + CredentialsSource credentialsSource + + def "should create file credentials source and basic authentication provider"() { + expect: + credentialsSource instanceof DatabaseCredentialsSource + verifyEach(authenticationProviders) { provider -> + provider.name != "anonymous" + !(provider instanceof AnonymousAuthenticationProvider) + } + } + } + + @TestPropertySource(properties = [ + "authentication.allow-anonymous=false", + "authentication.providers[0]=basic", + "authentication.credentials-sources[0]=file" + ]) + static class FileCredentialsSourceTest extends AuthenticationProviderTest { + @Autowired + CredentialsSource credentialsSource + + def "should create file credentials source and basic authentication provider"() { + expect: + (credentialsSource instanceof FileCredentialsSource) + and: + verifyEach(authenticationProviders) { provider -> + provider.name != "anonymous" + !(provider instanceof AnonymousAuthenticationProvider) + } + } + } + + @TestPropertySource(properties = [ + "authentication.allow-anonymous=true", + "authentication.providers[0]=basic", + "authentication.credentials-sources[0]=file" + ]) + static class AnonymousProviderTest extends AuthenticationProviderTest { + + def "should create file credentials source and basic authentication provider"() { + expect: + authenticationProviders.any { provider -> + provider.name == "anonymous" && provider instanceof AnonymousAuthenticationProvider + } + and: + authenticationProviders.any { provider -> + provider.name != "anonymous" && !(provider instanceof AnonymousAuthenticationProvider) + } + } + } + + @Ignore + @TestPropertySource(properties = "authentication.allow-anonymous=true") + static class AnonymousProvider2Test extends AuthenticationProviderTest { + + def "should create anonymous authentication provider"() { + expect: + authenticationProviders.any { provider -> + provider.name == "anonymous" && provider instanceof AnonymousAuthenticationProvider + } + and: + !authenticationProviders.any { provider -> + provider.name != "anonymous" && !(provider instanceof AnonymousAuthenticationProvider) + } + } + } + + @Ignore + static class EmptyProviderTest extends Specification { + + def "should fail start application context without any authentication provider"() { + given: + PropertySource propertySource = new PropertiesPropertySourceLoader() + .load("test-props", new ClassPathResource("application-test.properties")).getFirst() + def appContext = new ApplicationContextRunner() + .withAllowBeanDefinitionOverriding(true) + .withUserConfiguration(BasicAuthenticationSpringConfig, DatabaseSpringConfig) + .withInitializer { context -> + context.getEnvironment().getPropertySources().addLast(propertySource) + } + when: + appContext + .withPropertyValues("authentication.allow-anonymous=false") + .run({ context -> + if (context.startupFailure) { + throw context.startupFailure + } + }) + then: + def exception = thrown(Exception) + with(rootCauseOf(exception)) { rootCause -> + assert rootCause instanceof AuthenticationConfigException + assert message == "Authenticator providers are not configured" + } + } + } + + static Throwable rootCauseOf(Throwable throwable) { + Throwable rootCause = throwable; + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { + rootCause = rootCause.getCause(); + } + return rootCause; + } +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy new file mode 100644 index 00000000..a610de90 --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -0,0 +1,34 @@ +package javasabr.mqtt.broker.application.service + +import javasabr.mqtt.auth.api.AuthRequest +import javasabr.mqtt.auth.api.AuthenticationService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.TestPropertySource + +import java.nio.charset.StandardCharsets + +@TestPropertySource(properties = [ + "authentication.credentials-sources[0]=database", + "authentication.providers[0]=basic" +]) +class DatabaseAuthenticationServiceTest extends IntegrationSpecification { + + @Autowired + AuthenticationService authenticationService + + def "should authenticate credentials according [credentials/test] file"() { + given: + def passwordBytes = password.getBytes(StandardCharsets.UTF_8) + def request = new AuthRequest(userName, passwordBytes, "", new byte[0]) + when: + def result = authenticationService.authenticate(request).block() + then: + result == expectedResult + where: + userName | password | expectedResult + "user" | "wrong-password" | false + "user" | "correct-password" | true + "" | "correct-password" | false + "user" | "" | false + } +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy new file mode 100644 index 00000000..76bb9292 --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -0,0 +1,34 @@ +package javasabr.mqtt.broker.application.service + +import javasabr.mqtt.auth.api.AuthRequest +import javasabr.mqtt.auth.api.AuthenticationService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.TestPropertySource + +import java.nio.charset.StandardCharsets + +@TestPropertySource(properties = [ + "authentication.credentials-sources[0]=file", + "authentication.providers[0]=basic" +]) +class FileAuthenticationServiceTest extends IntegrationSpecification { + + @Autowired + AuthenticationService authenticationService + + def "should authenticate credentials according [credentials/test] file"() { + given: + def passwordBytes = password.getBytes(StandardCharsets.UTF_8) + def request = new AuthRequest(userName, passwordBytes, "", new byte[0]) + when: + def result = authenticationService.authenticate(request).block() + then: + result == expectedResult + where: + userName | password | expectedResult + "user" | "wrong-password" | false + "user" | "correct-password" | true + "" | "correct-password" | false + "user" | "" | false + } +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy new file mode 100644 index 00000000..50b9d00c --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy @@ -0,0 +1,17 @@ +package javasabr.mqtt.broker.application.service + + +import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig +import javasabr.mqtt.auth.service.config.DatabaseSpringConfig +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig +import spock.lang.Specification + +@TestPropertySource("classpath:application-test.properties") +@SpringJUnitConfig(classes = [ + BasicAuthenticationSpringConfig, + DatabaseSpringConfig, + DatabaseTestSpringConfig +]) +class IntegrationSpecification extends Specification { +} diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties new file mode 100644 index 00000000..1e9d2ea3 --- /dev/null +++ b/authentication-service/src/test/resources/application-test.properties @@ -0,0 +1,12 @@ +authentication.allow-anonymous=true +credentials.source.file.name=classpath:credentials/test +persistence.database.name=database-name +persistence.database.max-idle-time=30m +persistence.database.initial-pool-size=5 +persistence.database.max-pool-size=10 +persistence.database.lock-timeout=10s +persistence.database.statement-timeout=5m +persistence.database.users.admin.username=user +persistence.database.users.admin.password= +persistence.database.users.reader.username=user +persistence.database.users.reader.password= \ No newline at end of file diff --git a/authentication-service/src/test/resources/credentials/test b/authentication-service/src/test/resources/credentials/test new file mode 100644 index 00000000..5f2567ab --- /dev/null +++ b/authentication-service/src/test/resources/credentials/test @@ -0,0 +1,2 @@ +user=correct-password +user1=correct-password diff --git a/application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql b/authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql similarity index 100% rename from application/src/test/resources/db/migration/V1.1__Insert_Test_User.sql rename to authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy b/authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy similarity index 80% rename from application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy rename to authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy index 7b2a3e62..6d12c339 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/DatabaseTestSpringConfig.groovy +++ b/authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy @@ -1,10 +1,11 @@ //file:noinspection SpringJavaInjectionPointsAutowiringInspection -package javasabr.mqtt.broker.application.config +package javasabr.mqtt.broker.application.service import io.r2dbc.spi.ConnectionFactoryOptions import io.r2dbc.spi.Option -import javasabr.mqtt.auth.credentials.source.config.Credentials -import javasabr.mqtt.auth.credentials.source.config.DatabaseUrlBuilder +import javasabr.mqtt.auth.service.config.Credentials +import javasabr.mqtt.auth.service.config.DatabaseUrlBuilder +import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -16,6 +17,7 @@ import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL import static io.r2dbc.spi.ConnectionFactoryOptions.USER @Configuration +@ConditionalOnDatabaseCredentialsSource class DatabaseTestSpringConfig { @Bean From fbb05fa40f1228647bca500eecdd756129b19a37 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 12:23:59 +0100 Subject: [PATCH 039/107] [broker-141] Switch from H2 R2DBC to PostgreSQL Testcontainer --- application/build.gradle | 2 - authentication-service/build.gradle | 9 ++++- .../condition/AnonymousProviderCondition.java | 11 ++---- .../AuthenticationConfigCondition.java | 27 +++++++++++++ .../AuthenticationProviderCondition.java | 24 ++++-------- .../condition/CredentialsSourceCondition.java | 22 ++++------- .../V1__Create_User_Credentials_Table.sql | 2 +- .../service/AuthenticationProviderTest.groovy | 19 --------- .../DatabaseAuthenticationServiceTest.groovy | 37 ++++++++++++++++++ .../service/IntegrationSpecification.groovy | 3 +- .../resources/application-test.properties | 7 +++- .../db/migration/V1.1__Insert_Test_User.sql | 2 +- .../service/DatabaseTestSpringConfig.groovy | 39 ------------------- 13 files changed, 99 insertions(+), 105 deletions(-) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java delete mode 100644 authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy diff --git a/application/build.gradle b/application/build.gradle index 6a053148..a5653ee1 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -11,8 +11,6 @@ dependencies { implementation projects.coreService implementation projects.aclService implementation projects.aclGroovyDsl - implementation projects.authenticationProviderBasic - implementation projects.authenticationApi implementation projects.authenticationService implementation libs.rlib.logger.slf4j implementation libs.springboot.starter.core diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index d1064719..a5114663 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -21,6 +21,13 @@ dependencies { testImplementation projects.authenticationProviderBasic testImplementation libs.r2dbc.h2 + testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.5.8' + + testImplementation platform('org.testcontainers:testcontainers-bom:1.19.3') + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:postgresql' + testImplementation "org.testcontainers:testcontainers-spock:2.0.2" + testImplementation projects.testSupport testFixturesApi projects.testSupport @@ -31,5 +38,5 @@ dependencies { implementation libs.jdbc.postgres implementation libs.flyway.core implementation libs.flyway.postgresql - api libs.spring.r2dbc + implementation libs.spring.r2dbc } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java index 36c8972a..2cc3d0f6 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java @@ -12,13 +12,10 @@ public class AnonymousProviderCondition extends SpringBootCondition { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { return Binder.get(context.getEnvironment()) .bind("authentication", AuthenticationProperties.class) - .map(authProps -> { - if (authProps.allowAnonymous()) { - return ConditionOutcome.match("Anonymous connections allowed"); - } else { - return ConditionOutcome.noMatch("Anonymous connections denied"); - } - }) + .map(AuthenticationProperties::allowAnonymous) + .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled + ? ConditionOutcome.match("Anonymous connections allowed") + : ConditionOutcome.noMatch("Anonymous connections denied")) .orElse(ConditionOutcome.noMatch("Authentication providers are not configured")); } } \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java new file mode 100644 index 00000000..08dc0c70 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java @@ -0,0 +1,27 @@ +package javasabr.mqtt.auth.service.config.condition; + +import java.util.Map; +import java.util.Objects; +import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; + +public abstract class AuthenticationConfigCondition extends SpringBootCondition { + + public ConditionOutcome doFredAgain(Environment env, Map attributes, String resource) { + String id = Objects.requireNonNullElse(attributes, Map.of()) + .getOrDefault("value", "none") + .toString(); + return Binder.get(env) + .bind("authentication", AuthenticationProperties.class) + .map(sources -> isCredentialsSourceEnabled(sources, id)) + .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled + ? ConditionOutcome.match("%s '%s' enabled".formatted(resource, id)) + : ConditionOutcome.noMatch("%s '%s' disabled".formatted(resource, id))) + .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + } + + abstract boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value); +} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java index b32ef6e5..96e151df 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java @@ -2,30 +2,22 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import javasabr.mqtt.auth.service.config.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAuthenticationProvider; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; -public class AuthenticationProviderCondition extends SpringBootCondition { +public class AuthenticationProviderCondition extends AuthenticationConfigCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Map attributes = metadata.getAnnotationAttributes(ConditionalOnAuthenticationProvider.class.getName()); - String requiredProvider = attributes.getOrDefault("value", "").toString(); + Map attrs = metadata.getAnnotationAttributes(ConditionalOnAuthenticationProvider.class.getName()); + return doFredAgain(context.getEnvironment(), attrs, "Authentication Provider"); + } - return Binder.get(context.getEnvironment()) - .bind("authentication", AuthenticationProperties.class) - .map(authProps -> { - List providers = authProps.providers(); - if (providers != null && providers.contains(requiredProvider)) { - return ConditionOutcome.match("Provider '" + requiredProvider + "' found in MqttProperties"); - } else { - return ConditionOutcome.noMatch("Provider '" + requiredProvider + "' not active"); - } - }) - .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + @Override + boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value) { + return Objects.requireNonNullElse(properties.providers(), List.of()).contains(value); } } \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java index e5579deb..a93c23c1 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java @@ -2,30 +2,22 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import javasabr.mqtt.auth.service.config.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnCredentialsSource; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; -public class CredentialsSourceCondition extends SpringBootCondition { +public class CredentialsSourceCondition extends AuthenticationConfigCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Map attributes = metadata.getAnnotationAttributes(ConditionalOnCredentialsSource.class.getName()); - String requiredSource = attributes.getOrDefault("value", "").toString(); + return doFredAgain(context.getEnvironment(), attributes, "Credentials Source"); + } - return Binder.get(context.getEnvironment()) - .bind("authentication", AuthenticationProperties.class) - .map(authProps -> { - List sources = authProps.credentialsSources(); - if (sources != null && sources.contains(requiredSource)) { - return ConditionOutcome.match("Source '" + requiredSource + "' is enabled"); - } - return ConditionOutcome.noMatch("Source '" + requiredSource + "' not in config"); - }) - .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + @Override + boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value) { + return Objects.requireNonNullElse(properties.credentialsSources(), List.of()).contains(value); } } \ No newline at end of file diff --git a/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql index 1aa1b883..5d07ce1a 100644 --- a/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql +++ b/authentication-service/src/main/resources/db/migration/V1__Create_User_Credentials_Table.sql @@ -1,7 +1,7 @@ CREATE TABLE user_credentials ( username VARCHAR(255) NOT NULL PRIMARY KEY, - password VARBINARY(255) NOT NULL, + password BYTEA NOT NULL, CONSTRAINT username_not_empty CHECK (octet_length(username) > 0), CONSTRAINT password_not_empty CHECK (octet_length(password) > 0) ); diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index c5de0c71..f2418e86 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -23,25 +23,6 @@ class AuthenticationProviderTest extends IntegrationSpecification { @Autowired List authenticationProviders - @TestPropertySource(properties = [ - "authentication.allow-anonymous=false", - "authentication.providers[0]=basic", - "authentication.credentials-sources[0]=database" - ]) - static class DatabaseCredentialsSourceTest extends AuthenticationProviderTest { - @Autowired - CredentialsSource credentialsSource - - def "should create file credentials source and basic authentication provider"() { - expect: - credentialsSource instanceof DatabaseCredentialsSource - verifyEach(authenticationProviders) { provider -> - provider.name != "anonymous" - !(provider instanceof AnonymousAuthenticationProvider) - } - } - } - @TestPropertySource(properties = [ "authentication.allow-anonymous=false", "authentication.providers[0]=basic", diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index a610de90..13708db5 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -1,18 +1,46 @@ package javasabr.mqtt.broker.application.service +import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider import javasabr.mqtt.auth.api.AuthRequest +import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService +import javasabr.mqtt.auth.api.CredentialsSource +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource import org.springframework.test.context.TestPropertySource +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.spock.Testcontainers +import spock.lang.Shared import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ + "authentication.allow-anonymous=false", "authentication.credentials-sources[0]=database", "authentication.providers[0]=basic" ]) +@Testcontainers class DatabaseAuthenticationServiceTest extends IntegrationSpecification { + @Autowired + CredentialsSource credentialsSource + @Autowired + List authenticationProviders + + @Shared + static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.12") + .withDatabaseName("testdb") + .withUsername("user") + .withPassword("") + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + postgreSQLContainer.start() + registry.add("persistence.database.port", { "${postgreSQLContainer.getMappedPort(5432)}" }) + } + @Autowired AuthenticationService authenticationService @@ -31,4 +59,13 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { "" | "correct-password" | false "user" | "" | false } + + def "should create file credentials source and basic authentication provider"() { + expect: + credentialsSource instanceof DatabaseCredentialsSource + verifyEach(authenticationProviders) { provider -> + provider.name != "anonymous" + !(provider instanceof AnonymousAuthenticationProvider) + } + } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy index 50b9d00c..310108fe 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy @@ -10,8 +10,7 @@ import spock.lang.Specification @TestPropertySource("classpath:application-test.properties") @SpringJUnitConfig(classes = [ BasicAuthenticationSpringConfig, - DatabaseSpringConfig, - DatabaseTestSpringConfig + DatabaseSpringConfig ]) class IntegrationSpecification extends Specification { } diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index 1e9d2ea3..1b101da1 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,11 +1,14 @@ authentication.allow-anonymous=true credentials.source.file.name=classpath:credentials/test -persistence.database.name=database-name +persistence.database.driver=postgresql +persistence.database.host=localhost +persistence.database.port=5432 +persistence.database.name=testdb persistence.database.max-idle-time=30m persistence.database.initial-pool-size=5 persistence.database.max-pool-size=10 persistence.database.lock-timeout=10s -persistence.database.statement-timeout=5m +persistence.database.statement-timeout=5min persistence.database.users.admin.username=user persistence.database.users.admin.password= persistence.database.users.reader.username=user diff --git a/authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql b/authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql index 48da9df3..9e985d0b 100644 --- a/authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql +++ b/authentication-service/src/test/resources/db/migration/V1.1__Insert_Test_User.sql @@ -1,2 +1,2 @@ INSERT INTO user_credentials(username, password) -VALUES ('user', X'63 6f 72 72 65 63 74 2d 70 61 73 73 77 6f 72 64'); +VALUES ('user', '\x636f72726563742d70617373776f7264'); diff --git a/authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy b/authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy deleted file mode 100644 index 6d12c339..00000000 --- a/authentication-service/src/testFixtures/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy +++ /dev/null @@ -1,39 +0,0 @@ -//file:noinspection SpringJavaInjectionPointsAutowiringInspection -package javasabr.mqtt.broker.application.service - -import io.r2dbc.spi.ConnectionFactoryOptions -import io.r2dbc.spi.Option -import javasabr.mqtt.auth.service.config.Credentials -import javasabr.mqtt.auth.service.config.DatabaseUrlBuilder -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD -import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL -import static io.r2dbc.spi.ConnectionFactoryOptions.USER - -@Configuration -@ConditionalOnDatabaseCredentialsSource -class DatabaseTestSpringConfig { - - @Bean - ConnectionFactoryOptions connectionFactoryOptions(@Qualifier("readerCredentials") Credentials credentials) { - return ConnectionFactoryOptions.builder() - .option(DRIVER, "h2") - .option(PROTOCOL, "mem") - .option(DATABASE, "testdb") - .option(USER, credentials.username()) - .option(PASSWORD, credentials.password()) - .option(Option.valueOf("DB_CLOSE_DELAY"), "-1") - .build() - } - - @Bean - DatabaseUrlBuilder databaseUrlBuilder() { - return { "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" } - } -} From 785c8dcb9474cb3b5ba7fdd7d7bef1637de3dbdb Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 15:43:55 +0100 Subject: [PATCH 040/107] [broker-141] Improve AuthenticationConfigCondition abstraction, refactoring --- .../config/MqttBrokerSpringConfig.java | 6 ++-- .../ContextRunnerSpecification.groovy | 36 ------------------- .../config/MqttBrokerTestConfig.groovy | 7 +--- .../api/AnonymousAuthenticationProvider.java | 8 ++++- .../provider/BasicAuthenticationProvider.java | 4 ++- authentication-service/build.gradle | 2 ++ .../service/DefaultAuthenticationService.java | 20 ++++++++--- ...=> AuthenticationServiceSpringConfig.java} | 9 ++--- .../service/config/DatabaseSpringConfig.java | 5 +-- .../ConditionalOnAnonymousProvider.java | 2 +- .../ConditionalOnAuthenticationProvider.java | 13 ++++--- .../ConditionalOnCredentialsSource.java | 4 ++- .../condition/AnonymousProviderCondition.java | 4 +-- .../AuthenticationConfigCondition.java | 28 ++++++++++----- .../AuthenticationProviderCondition.java | 14 +++----- .../condition/CredentialsSourceCondition.java | 14 +++----- .../src/main/resources/log4j2.xml | 13 +++++++ .../service/AuthenticationProviderTest.groovy | 6 ++-- .../service/IntegrationSpecification.groovy | 9 ++--- .../source/DatabaseCredentialsSource.java | 2 ++ .../source/FileCredentialsSource.java | 4 ++- 21 files changed, 99 insertions(+), 111 deletions(-) delete mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{BasicAuthenticationSpringConfig.java => AuthenticationServiceSpringConfig.java} (94%) create mode 100644 authentication-service/src/main/resources/log4j2.xml diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 2710042e..e902766f 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -5,8 +5,7 @@ import java.util.List; import javasabr.mqtt.acl.service.conifg.GroovyDslBasedAclServiceSpringConfig; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig; -import javasabr.mqtt.auth.service.config.DatabaseSpringConfig; +import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -82,8 +81,7 @@ @Import({ GroovyDslBasedAclServiceSpringConfig.class, - DatabaseSpringConfig.class, - BasicAuthenticationSpringConfig.class + AuthenticationServiceSpringConfig.class }) @CustomLog @Configuration(proxyBeanMethods = false) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy deleted file mode 100644 index e0b3cf8e..00000000 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ContextRunnerSpecification.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package javasabr.mqtt.broker.application - -import org.springframework.boot.env.PropertiesPropertySourceLoader -import org.springframework.boot.test.context.runner.ApplicationContextRunner -import org.springframework.core.env.PropertySource -import org.springframework.core.io.ClassPathResource -import spock.lang.Specification - -import static javasabr.mqtt.broker.application.MqttClientFactory.generateClientId - -abstract class ContextRunnerSpecification extends Specification { - - ApplicationContextRunner contextRunner - - def createContextRunner(Class springConfigClass, String applicationPropertiesFile) { - PropertySource propertySource = new PropertiesPropertySourceLoader() - .load("test-props", new ClassPathResource(applicationPropertiesFile)).getFirst() - contextRunner = new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withUserConfiguration(springConfigClass) - .withInitializer { context -> - context.getEnvironment().getPropertySources().addLast(propertySource) - } - } - - void runContextWithProperties(String[] properties, Closure clientConstructor, Closure assertion) { - Objects.requireNonNull( - contextRunner, - "ApplicationContextRunner is not initialized. See `ApplicationPropertiesSpecification.createContextRunner`") - contextRunner - .withPropertyValues(properties) - .run({ ctx -> - assertion(clientConstructor(generateClientId("ApplicationContextRunner"), ctx.getBean(InetSocketAddress))) - }) - } -} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy index 89470e89..3142b76f 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/config/MqttBrokerTestConfig.groovy @@ -1,11 +1,7 @@ //file:noinspection SpringJavaInjectionPointsAutowiringInspection package javasabr.mqtt.broker.application.config - import javasabr.mqtt.network.MqttConnection - -//import javasabr.mqtt.broker.application.service.DatabaseTestSpringConfig - import javasabr.mqtt.service.ConnectionService import javasabr.rlib.network.server.ServerNetwork import org.springframework.boot.context.event.ApplicationStartedEvent @@ -17,8 +13,7 @@ import org.springframework.context.annotation.Import import java.util.concurrent.ThreadLocalRandom @Import([ - MqttBrokerSpringConfig, -// DatabaseTestSpringConfig + MqttBrokerSpringConfig ]) @Configuration(proxyBeanMethods = false) class MqttBrokerTestConfig { diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java index 2ecdf825..4c18a7be 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java @@ -1,7 +1,7 @@ package javasabr.mqtt.auth.api; +import com.fasterxml.jackson.annotation.JsonValue; import javasabr.rlib.common.util.StringUtils; -import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; public class AnonymousAuthenticationProvider implements AuthenticationProvider { @@ -14,4 +14,10 @@ public String getName() { public Mono authenticate(String username, byte[] password, byte[] data) { return Mono.just(StringUtils.isEmpty(username)); } + + @JsonValue + @Override + public String toString() { + return "\"enabled\""; + } } diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index c14e45e7..5c27e2fd 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -1,5 +1,6 @@ package javasabr.mqtt.auth.provider; +import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; @@ -23,8 +24,9 @@ public Mono authenticate(String username, byte[] password, byte[] data) return credentialsSource.isCredentialsExists(username, password); } + @JsonValue @Override public String toString() { - return "\"%s\": { \"credentialSource\": %s }".formatted(getName(), credentialsSource); + return "{ \"credentialSource\": %s }".formatted(credentialsSource); } } diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index a5114663..1fd5107e 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -27,6 +27,8 @@ dependencies { testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:postgresql' testImplementation "org.testcontainers:testcontainers-spock:2.0.2" + implementation libs.springboot.starter.log4j2 + implementation libs.rlib.logger.slf4j testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 77e92cd3..412c5027 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -36,20 +36,32 @@ private static String buildServiceDescription( var builder = new StringBuilder() .append("{\n") - .append(" \"DEFAULT\": \"") + .append(" \"default\": \"") .append(defaultProvider.getName()) .append("\",\n"); - + if (!providers.isEmpty()) { + builder + .append(" \"") + .append("available") + .append("\": {") + .append("\n"); + } for (AuthenticationProvider provider : providers) { builder - .append(" ") + .append(" \"") + .append(provider.getName()) + .append("\": ") .append(provider) .append(",") .append("\n"); } builder .delete(builder.length() - 2, builder.length()) - .append("\n}"); + .append("\n"); + if (!providers.isEmpty()) { + builder.append(" }\n"); + } + builder.append("}"); return "Loaded total [%s] authentication providers: %s".formatted(providers.size(), builder); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java similarity index 94% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 60c61fd8..b39d391f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/BasicAuthenticationSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -1,6 +1,5 @@ package javasabr.mqtt.auth.service.config; -import io.r2dbc.spi.ConnectionFactory; import java.net.URI; import java.util.List; import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; @@ -10,8 +9,6 @@ import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; -import javasabr.mqtt.auth.service.config.DatabaseUrlBuilder; -import javasabr.mqtt.auth.service.config.DatabaseUrlConfig; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAnonymousProvider; @@ -25,14 +22,16 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.r2dbc.core.DatabaseClient; @CustomLog @Configuration(proxyBeanMethods = false) +@Import(DatabaseSpringConfig.class) @EnableConfigurationProperties({ AuthenticationProperties.class }) -public class BasicAuthenticationSpringConfig { +public class AuthenticationServiceSpringConfig { @Bean AuthenticationService authenticationService( @@ -56,8 +55,6 @@ AuthenticationService authenticationService( return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider); } - - @Bean @ConditionalOnFileCredentialsSource CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 85e7b113..8fc3db13 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -24,9 +24,7 @@ import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ - DatabaseConnectionProperties.class -}) +@EnableConfigurationProperties(DatabaseConnectionProperties.class) @ConditionalOnDatabaseCredentialsSource public class DatabaseSpringConfig { @@ -55,7 +53,6 @@ DatabaseUrlBuilder databaseUrlBuilder() { } @Bean - @ConditionalOnDatabaseCredentialsSource DatabaseClient databaseClient(ConnectionFactory connectionFactory) { return DatabaseClient.create(connectionFactory); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java index 719b3067..751eaee7 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java @@ -11,4 +11,4 @@ @Retention(RetentionPolicy.RUNTIME) @Conditional(AnonymousProviderCondition.class) public @interface ConditionalOnAnonymousProvider { -} \ No newline at end of file +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java index 71112ccd..503bce4c 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java @@ -1,12 +1,17 @@ package javasabr.mqtt.auth.service.config.annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import javasabr.mqtt.auth.service.config.condition.AuthenticationProviderCondition; import org.springframework.context.annotation.Conditional; -import java.lang.annotation.*; -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(AuthenticationProviderCondition.class) public @interface ConditionalOnAuthenticationProvider { - String value(); -} \ No newline at end of file + String value(); + + String resource() default "Authentication Provider"; +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java index 955f0233..5c914eec 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java @@ -12,4 +12,6 @@ @Conditional(CredentialsSourceCondition.class) public @interface ConditionalOnCredentialsSource { String value(); -} \ No newline at end of file + + String message() default "Credentials Source"; +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java index 2cc3d0f6..9d9e609e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java @@ -16,6 +16,6 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled ? ConditionOutcome.match("Anonymous connections allowed") : ConditionOutcome.noMatch("Anonymous connections denied")) - .orElse(ConditionOutcome.noMatch("Authentication providers are not configured")); + .orElse(ConditionOutcome.noMatch("Authentication properties not defined")); } -} \ No newline at end of file +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java index 08dc0c70..8bece79e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java @@ -6,22 +6,32 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.core.env.Environment; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; public abstract class AuthenticationConfigCondition extends SpringBootCondition { - public ConditionOutcome doFredAgain(Environment env, Map attributes, String resource) { - String id = Objects.requireNonNullElse(attributes, Map.of()) + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(annotation().getName()); + attributes = Objects.requireNonNullElse(attributes, Map.of()); + String resourceId = attributes .getOrDefault("value", "none") .toString(); - return Binder.get(env) + String resource = attributes + .getOrDefault("resource", "unknown") + .toString(); + return Binder.get(context.getEnvironment()) .bind("authentication", AuthenticationProperties.class) - .map(sources -> isCredentialsSourceEnabled(sources, id)) + .map(properties -> isEnabled(properties, resourceId)) .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled - ? ConditionOutcome.match("%s '%s' enabled".formatted(resource, id)) - : ConditionOutcome.noMatch("%s '%s' disabled".formatted(resource, id))) - .orElse(ConditionOutcome.noMatch("Authentication properties not found")); + ? ConditionOutcome.match("%s '%s' enabled".formatted(resource, resourceId)) + : ConditionOutcome.noMatch("%s '%s' disabled".formatted(resource, resourceId))) + .orElse(ConditionOutcome.noMatch("Authentication properties not defined")); + } - abstract boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value); + abstract boolean isEnabled(AuthenticationProperties properties, String value); + + abstract Class annotation(); } \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java index 96e151df..b10986f6 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java @@ -1,23 +1,17 @@ package javasabr.mqtt.auth.service.config.condition; import java.util.List; -import java.util.Map; import java.util.Objects; import javasabr.mqtt.auth.service.config.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAuthenticationProvider; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; public class AuthenticationProviderCondition extends AuthenticationConfigCondition { @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Map attrs = metadata.getAnnotationAttributes(ConditionalOnAuthenticationProvider.class.getName()); - return doFredAgain(context.getEnvironment(), attrs, "Authentication Provider"); + boolean isEnabled(AuthenticationProperties properties, String value) { + return Objects.requireNonNullElse(properties.providers(), List.of()).contains(value); } - @Override - boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value) { - return Objects.requireNonNullElse(properties.providers(), List.of()).contains(value); + Class annotation() { + return ConditionalOnAuthenticationProvider.class; } } \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java index a93c23c1..581d28cd 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java @@ -1,23 +1,17 @@ package javasabr.mqtt.auth.service.config.condition; import java.util.List; -import java.util.Map; import java.util.Objects; import javasabr.mqtt.auth.service.config.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnCredentialsSource; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; public class CredentialsSourceCondition extends AuthenticationConfigCondition { @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Map attributes = metadata.getAnnotationAttributes(ConditionalOnCredentialsSource.class.getName()); - return doFredAgain(context.getEnvironment(), attributes, "Credentials Source"); + boolean isEnabled(AuthenticationProperties properties, String value) { + return Objects.requireNonNullElse(properties.credentialsSources(), List.of()).contains(value); } - @Override - boolean isCredentialsSourceEnabled(AuthenticationProperties properties, String value) { - return Objects.requireNonNullElse(properties.credentialsSources(), List.of()).contains(value); + Class annotation() { + return ConditionalOnCredentialsSource.class; } } \ No newline at end of file diff --git a/authentication-service/src/main/resources/log4j2.xml b/authentication-service/src/main/resources/log4j2.xml new file mode 100644 index 00000000..ceb3b07e --- /dev/null +++ b/authentication-service/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index f2418e86..e6bb120a 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -5,10 +5,8 @@ import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.exception.AuthenticationConfigException -import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource import javasabr.mqtt.auth.credentials.source.FileCredentialsSource -import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig -import javasabr.mqtt.auth.service.config.DatabaseSpringConfig +import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.env.PropertiesPropertySourceLoader import org.springframework.boot.test.context.runner.ApplicationContextRunner @@ -87,7 +85,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { .load("test-props", new ClassPathResource("application-test.properties")).getFirst() def appContext = new ApplicationContextRunner() .withAllowBeanDefinitionOverriding(true) - .withUserConfiguration(BasicAuthenticationSpringConfig, DatabaseSpringConfig) + .withUserConfiguration(AuthenticationServiceSpringConfig) .withInitializer { context -> context.getEnvironment().getPropertySources().addLast(propertySource) } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy index 310108fe..366b1109 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy @@ -1,16 +1,11 @@ package javasabr.mqtt.broker.application.service - -import javasabr.mqtt.auth.service.config.BasicAuthenticationSpringConfig -import javasabr.mqtt.auth.service.config.DatabaseSpringConfig +import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import spock.lang.Specification @TestPropertySource("classpath:application-test.properties") -@SpringJUnitConfig(classes = [ - BasicAuthenticationSpringConfig, - DatabaseSpringConfig -]) +@SpringJUnitConfig(classes = AuthenticationServiceSpringConfig) class IntegrationSpecification extends Specification { } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 2fcc9e0f..0d822cd8 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,5 +1,6 @@ package javasabr.mqtt.auth.credentials.source; +import com.fasterxml.jackson.annotation.JsonValue; import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; @@ -39,6 +40,7 @@ public Mono isCredentialsExists(String userName, byte[] password) { .defaultIfEmpty(false); } + @JsonValue @Override public String toString() { return "{ \"%s\": \"%s\" }".formatted(getName(), dbUrl); diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index b75498b9..99a8f7cc 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -1,11 +1,12 @@ package javasabr.mqtt.auth.credentials.source; +import com.fasterxml.jackson.annotation.JsonValue; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import javasabr.mqtt.auth.api.InMemoryCredentialsSource; +import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -33,6 +34,7 @@ public String getName() { return "file"; } + @JsonValue @Override public String toString() { return "{ \"%s\": \"%s\" }".formatted(getName(), fileName.getPath()); From a0644290b27d4224bb0e82da187a16e308ba2177 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 17:22:17 +0100 Subject: [PATCH 041/107] [broker-141] Improve dependency declaration --- .../application/MqttClientFactory.groovy | 6 ++-- authentication-service/build.gradle | 30 +++++++------------ gradle/libs.versions.toml | 6 ++-- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy index 6e315bc6..dad245e9 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/MqttClientFactory.groovy @@ -6,9 +6,9 @@ import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient import java.util.concurrent.atomic.AtomicInteger -public class MqttClientFactory { +class MqttClientFactory { - private static final idGenerator = new AtomicInteger(1) + private static final ID_GENERATOR = new AtomicInteger(1) static Mqtt5AsyncClient buildMqtt5Client(String clientId, InetSocketAddress address) { return MqttClient.builder() @@ -37,6 +37,6 @@ public class MqttClientFactory { } static String generateClientId(String prefix) { - return prefix + "_" + idGenerator.incrementAndGet() + return prefix + "_" + ID_GENERATOR.incrementAndGet() } } diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index 1fd5107e..4dbad706 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -10,30 +10,13 @@ dependencies { api projects.base api projects.model api projects.authenticationApi - api libs.springboot.starter.autoconfigure - compileOnlyApi projects.credentialsSourceDb compileOnlyApi projects.credentialsSourceFile compileOnlyApi projects.authenticationProviderBasic - - testImplementation projects.credentialsSourceDb - testImplementation projects.credentialsSourceFile - testImplementation projects.authenticationProviderBasic - testImplementation libs.r2dbc.h2 - - testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.5.8' - - testImplementation platform('org.testcontainers:testcontainers-bom:1.19.3') - testImplementation 'org.testcontainers:testcontainers' - testImplementation 'org.testcontainers:postgresql' - testImplementation "org.testcontainers:testcontainers-spock:2.0.2" + implementation libs.springboot.starter.autoconfigure implementation libs.springboot.starter.log4j2 implementation libs.rlib.logger.slf4j - - testImplementation projects.testSupport - testFixturesApi projects.testSupport - -// database support + // database support implementation libs.r2dbc.spi implementation libs.r2dbc.pool implementation libs.r2dbc.postgresql @@ -41,4 +24,13 @@ dependencies { implementation libs.flyway.core implementation libs.flyway.postgresql implementation libs.spring.r2dbc + // test support + testImplementation projects.credentialsSourceDb + testImplementation projects.credentialsSourceFile + testImplementation projects.authenticationProviderBasic + testImplementation libs.testcontainers.core + testImplementation libs.testcontainers.postgresql + testImplementation libs.testcontainers.spock + testImplementation projects.testSupport + testFixturesApi projects.testSupport } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d9f6c2e..82b2f612 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ lombok = "1.18.38" # https://mvnrepository.com/artifact/org.jspecify/jspecify jspecify = "1.0.0" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers -testcontainers = "1.21.3" +testcontainers = "2.0.3" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine junit-jupiter = "5.13.4" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher @@ -58,8 +58,10 @@ springboot-starter-autoconfigure = { module = "org.springframework.boot:spring-b springboot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2", version.ref = "springboot" } springboot-test = { module = "org.springframework.boot:spring-boot-test", version.ref = "springboot" } assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" } +testcontainers-core = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers"} +testcontainers-spock = { module = "org.testcontainers:testcontainers-spock", version.ref = "testcontainers"} +testcontainers-postgresql = { module = "org.testcontainers:testcontainers-postgresql", version.ref = "testcontainers"} project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" } -r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "r2dbc" } r2dbc-spi = { module ='io.r2dbc:r2dbc-spi', version.ref = "r2dbc"} r2dbc-pool = { module = 'io.r2dbc:r2dbc-pool', version.ref = "r2dbc-pool"} r2dbc-postgresql = { module = 'org.postgresql:r2dbc-postgresql', version.ref = "r2dbc-postgresql"} From 5ae4d6500e60e84c1390e73b91a4d47f5f94fb34 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 17:52:49 +0100 Subject: [PATCH 042/107] [broker-141] Improve DefaultAuthenticationService description builder --- .../mqtt/auth/service/DefaultAuthenticationService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 412c5027..4d41568e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -43,8 +43,7 @@ private static String buildServiceDescription( builder .append(" \"") .append("available") - .append("\": {") - .append("\n"); + .append("\": {\n"); } for (AuthenticationProvider provider : providers) { builder @@ -52,8 +51,7 @@ private static String buildServiceDescription( .append(provider.getName()) .append("\": ") .append(provider) - .append(",") - .append("\n"); + .append(",\n"); } builder .delete(builder.length() - 2, builder.length()) From b142f23b338229106f5411c2a19a57418c79e224 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:51:17 +0100 Subject: [PATCH 043/107] [broker-141] Remove redundant code, refactoring --- .../config/AuthenticationProperties.java | 21 ------------------- .../AuthenticationServiceSpringConfig.java | 6 +++--- .../service/config/DatabaseSpringConfig.java | 21 +++++++++++++------ .../service/config/DatabaseUrlBuilder.java | 2 ++ .../service/config/DatabaseUsersConfig.java | 7 +------ .../condition/AnonymousProviderCondition.java | 2 +- .../AuthenticationConfigCondition.java | 2 +- .../AuthenticationProviderCondition.java | 2 +- .../condition/CredentialsSourceCondition.java | 2 +- .../property/AuthenticationProperties.java | 12 +++++++++++ .../config/{ => property}/Credentials.java | 2 +- .../DatabaseConnectionProperties.java | 3 ++- .../{ => property}/DatabasePoolConfig.java | 2 +- .../DatabaseTimeoutsConfig.java | 2 +- .../{ => property}/DatabaseUrlConfig.java | 2 +- .../service/AuthenticationProviderTest.groovy | 6 ++---- .../resources/application-test.properties | 3 +-- 17 files changed, 46 insertions(+), 51 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{ => property}/Credentials.java (55%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{ => property}/DatabaseConnectionProperties.java (83%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{ => property}/DatabasePoolConfig.java (73%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{ => property}/DatabaseTimeoutsConfig.java (66%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{ => property}/DatabaseUrlConfig.java (68%) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java deleted file mode 100644 index 76ca7ebf..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package javasabr.mqtt.auth.service.config; - -import java.util.List; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -import org.jspecify.annotations.Nullable; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -@ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties( - boolean allowAnonymous, - @Nullable String defaultProvider, - @Nullable List providers, - @Nullable List credentialsSources) { - - public AuthenticationProperties { - if (!allowAnonymous && (providers == null || providers.isEmpty())) { - throw new AuthenticationConfigException("Authenticator providers are not configured"); - } - } -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index b39d391f..c3ff15bb 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -15,6 +15,8 @@ import javasabr.mqtt.auth.service.config.annotation.ConditionalOnBasicAuthenticationProvider; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnFileCredentialsSource; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import javasabr.rlib.collections.dictionary.DictionaryFactory; import lombok.CustomLog; import org.jspecify.annotations.Nullable; @@ -28,9 +30,7 @@ @CustomLog @Configuration(proxyBeanMethods = false) @Import(DatabaseSpringConfig.class) -@EnableConfigurationProperties({ - AuthenticationProperties.class -}) +@EnableConfigurationProperties(AuthenticationProperties.class) public class AuthenticationServiceSpringConfig { @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 8fc3db13..7fc74b7e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -15,8 +15,14 @@ import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; +import javasabr.mqtt.auth.service.config.property.Credentials; +import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; +import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; +import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; +import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -81,17 +87,20 @@ Flyway flyway( } @Bean - public Credentials readerCredentials(DatabaseUsersConfig users) { - return users.get("reader"); + @ConditionalOnProperty(name = "persistence.database.users.reader.username") + public Credentials readerCredentials(DatabaseUsersConfig usersConfig) { + return usersConfig.users().get("reader"); } @Bean - public Credentials writerCredentials(DatabaseUsersConfig users) { - return users.get("writer"); + @ConditionalOnProperty(name = "persistence.database.users.writer.username") + public Credentials writerCredentials(DatabaseUsersConfig usersConfig) { + return usersConfig.users().get("writer"); } @Bean - public Credentials adminCredentials(DatabaseUsersConfig users) { - return users.get("admin"); + @ConditionalOnProperty(name = "persistence.database.users.admin.username") + public Credentials adminCredentials(DatabaseUsersConfig usersConfig) { + return usersConfig.users().get("admin"); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java index 3165b062..499aa56d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java @@ -1,5 +1,7 @@ package javasabr.mqtt.auth.service.config; +import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; + public interface DatabaseUrlBuilder { String build(DatabaseUrlConfig databaseUrlConfig); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java index b494467f..3c1a1bed 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java @@ -1,15 +1,10 @@ package javasabr.mqtt.auth.service.config; import java.util.Map; +import javasabr.mqtt.auth.service.config.property.Credentials; public interface DatabaseUsersConfig { - Credentials ANONYMOUS_CREDENTIAL = new Credentials("", ""); - Map users(); - - default Credentials get(String username) { - return users().getOrDefault(username, ANONYMOUS_CREDENTIAL); - } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java index 9d9e609e..e3c7df01 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service.config.condition; -import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.context.properties.bind.Binder; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java index 8bece79e..8fb705ae 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java @@ -2,7 +2,7 @@ import java.util.Map; import java.util.Objects; -import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.context.properties.bind.Binder; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java index b10986f6..56b5a2d4 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.Objects; -import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAuthenticationProvider; public class AuthenticationProviderCondition extends AuthenticationConfigCondition { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java index 581d28cd..787d5bf9 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.Objects; -import javasabr.mqtt.auth.service.config.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnCredentialsSource; public class CredentialsSourceCondition extends AuthenticationConfigCondition { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java new file mode 100644 index 00000000..a1e4f1bf --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.util.List; +import org.jspecify.annotations.Nullable; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "authentication") +public record AuthenticationProperties( + boolean allowAnonymous, + @Nullable String defaultProvider, + @Nullable List providers, + @Nullable List credentialsSources) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java similarity index 55% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java index eaadfdba..e763a7f1 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/Credentials.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.auth.service.config.property; public record Credentials(String username, String password) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java similarity index 83% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java index 27ff7a1b..b1b31ec3 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseConnectionProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java @@ -1,7 +1,8 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.auth.service.config.property; import java.time.Duration; import java.util.Map; +import javasabr.mqtt.auth.service.config.DatabaseUsersConfig; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "persistence.database") diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java similarity index 73% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java index e9b7a80d..eaee3a75 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabasePoolConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.auth.service.config.property; import java.time.Duration; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java similarity index 66% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java index e0781a5f..1e1b0012 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseTimeoutsConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.auth.service.config.property; public interface DatabaseTimeoutsConfig { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java similarity index 68% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java index 5792fa86..5545e662 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config; +package javasabr.mqtt.auth.service.config.property; public interface DatabaseUrlConfig { String driver(); diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index e6bb120a..29769729 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -60,7 +60,6 @@ class AuthenticationProviderTest extends IntegrationSpecification { } } - @Ignore @TestPropertySource(properties = "authentication.allow-anonymous=true") static class AnonymousProvider2Test extends AuthenticationProviderTest { @@ -76,7 +75,6 @@ class AuthenticationProviderTest extends IntegrationSpecification { } } - @Ignore static class EmptyProviderTest extends Specification { def "should fail start application context without any authentication provider"() { @@ -107,10 +105,10 @@ class AuthenticationProviderTest extends IntegrationSpecification { } static Throwable rootCauseOf(Throwable throwable) { - Throwable rootCause = throwable; + Throwable rootCause = throwable while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { rootCause = rootCause.getCause(); } - return rootCause; + return rootCause } } diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index 1b101da1..be5de1f2 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,4 +1,3 @@ -authentication.allow-anonymous=true credentials.source.file.name=classpath:credentials/test persistence.database.driver=postgresql persistence.database.host=localhost @@ -12,4 +11,4 @@ persistence.database.statement-timeout=5min persistence.database.users.admin.username=user persistence.database.users.admin.password= persistence.database.users.reader.username=user -persistence.database.users.reader.password= \ No newline at end of file +persistence.database.users.reader.password= From 51823b51fd52721b01b95a27bfd51d84d4a5cdcb Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:29:43 +0100 Subject: [PATCH 044/107] [broker-141] Support multi-provider setup --- application/build.gradle | 2 + .../application/ExternalConnectionTest.groovy | 4 +- .../resources/application-test.properties | 2 + .../src/test/resources/auth/credentials-test | 2 +- .../mqtt/auth/api/AuthenticationProvider.java | 2 +- .../mqtt/auth/api/AuthenticationType.java | 21 +++++++++ .../provider/BasicAuthenticationProvider.java | 5 +- .../AnonymousAuthenticationProvider.java | 10 ++-- .../service/DefaultAuthenticationService.java | 47 +++++++------------ .../AuthenticationServiceSpringConfig.java | 26 +++------- .../property/AuthenticationProperties.java | 1 - .../service/AuthenticationProviderTest.groovy | 15 +++--- .../DatabaseAuthenticationServiceTest.groovy | 7 +-- 13 files changed, 75 insertions(+), 69 deletions(-) create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java rename {authentication-api/src/main/java/javasabr/mqtt/auth/api => authentication-service/src/main/java/javasabr/mqtt/auth/service}/AnonymousAuthenticationProvider.java (63%) diff --git a/application/build.gradle b/application/build.gradle index a5653ee1..a06ceee7 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -18,6 +18,8 @@ dependencies { testImplementation projects.testSupport testImplementation testFixtures(projects.network) + testImplementation projects.credentialsSourceFile + testImplementation projects.authenticationProviderBasic } tasks.withType(GroovyCompile).configureEach { diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy index bf46d0fc..71384b82 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy @@ -45,7 +45,7 @@ class ExternalConnectionTest extends IntegrationSpecification { given: def client = buildExternalMqtt311Client() when: - def result = connectWith(client, '', '') + def result = connectWith(client, 'user1', 'password') then: result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS !result.sessionPresent @@ -57,7 +57,7 @@ class ExternalConnectionTest extends IntegrationSpecification { given: def client = buildExternalMqtt5Client() when: - def result = connectWith(client, '', '') + def result = connectWith(client, 'user1', 'password') then: result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS result.sessionExpiryInterval.present diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index e70e80cf..51aa8083 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,4 +1,6 @@ authentication.allow-anonymous=true +authentication.providers[0]=basic +authentication.credentials-sources[0]=file credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/application/src/test/resources/auth/credentials-test b/application/src/test/resources/auth/credentials-test index 5f2567ab..ef66d807 100644 --- a/application/src/test/resources/auth/credentials-test +++ b/application/src/test/resources/auth/credentials-test @@ -1,2 +1,2 @@ user=correct-password -user1=correct-password +user1=password diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index 371534d6..0cc4452f 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -4,7 +4,7 @@ public interface AuthenticationProvider { - String getName(); + AuthenticationType getAuthenticationType(); Mono authenticate(String username, byte[] password, byte[] data); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java new file mode 100644 index 00000000..f1ac4124 --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java @@ -0,0 +1,21 @@ +package javasabr.mqtt.auth.api; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Getter +@Accessors +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum AuthenticationType { + X509(1), + ENHANCED(2), + JWT(3), + BASIC(4), + ANONYMOUS(5); + + int priority; +} diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 5c27e2fd..de85134c 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -15,8 +16,8 @@ public class BasicAuthenticationProvider implements AuthenticationProvider { CredentialsSource credentialsSource; @Override - public String getName() { - return "basic"; + public AuthenticationType getAuthenticationType() { + return AuthenticationType.BASIC; } @Override diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java similarity index 63% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index 4c18a7be..19c5b1c0 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -1,13 +1,15 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.service; import com.fasterxml.jackson.annotation.JsonValue; +import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.rlib.common.util.StringUtils; import reactor.core.publisher.Mono; public class AnonymousAuthenticationProvider implements AuthenticationProvider { @Override - public String getName() { - return "anonymous"; + public AuthenticationType getAuthenticationType() { + return AuthenticationType.ANONYMOUS; } @Override @@ -18,6 +20,6 @@ public Mono authenticate(String username, byte[] password, byte[] data) @JsonValue @Override public String toString() { - return "\"enabled\""; + return "true"; } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 4d41568e..b96c9832 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -3,63 +3,52 @@ import javasabr.mqtt.auth.api.AuthRequest; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @CustomLog @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - RefToRefDictionary providers; - AuthenticationProvider defaultProvider; + Array providers; - public DefaultAuthenticationService( - RefToRefDictionary providers, - AuthenticationProvider defaultProvider) { + public DefaultAuthenticationService(Array providers) { this.providers = providers; - this.defaultProvider = defaultProvider; - log.info(providers, defaultProvider, DefaultAuthenticationService::buildServiceDescription); + log.info(providers, DefaultAuthenticationService::buildServiceDescription); } @Override public Mono authenticate(AuthRequest request) { - return providers.getOrDefault(request.authenticationMethod(), defaultProvider) - .authenticate(request.username(), request.password(), request.authenticationData()); + return Flux.fromIterable(providers) + .concatMap(manager -> manager.authenticate( + request.username(), + request.password(), + request.authenticationData()) + .onErrorReturn(false) + ) + .any(Boolean::booleanValue); } private static String buildServiceDescription( - RefToRefDictionary providers, - AuthenticationProvider defaultProvider) { + Array providers) { var builder = new StringBuilder() - .append("{\n") - .append(" \"default\": \"") - .append(defaultProvider.getName()) - .append("\",\n"); - if (!providers.isEmpty()) { - builder - .append(" \"") - .append("available") - .append("\": {\n"); - } + .append("{\n"); for (AuthenticationProvider provider : providers) { builder - .append(" \"") - .append(provider.getName()) + .append(" \"") + .append(provider.getAuthenticationType()) .append("\": ") .append(provider) .append(",\n"); } builder .delete(builder.length() - 2, builder.length()) - .append("\n"); - if (!providers.isEmpty()) { - builder.append(" }\n"); - } - builder.append("}"); + .append("\n}"); return "Loaded total [%s] authentication providers: %s".formatted(providers.size(), builder); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index c3ff15bb..40823ccf 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -1,8 +1,8 @@ package javasabr.mqtt.auth.service.config; import java.net.URI; +import java.util.Comparator; import java.util.List; -import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; @@ -10,6 +10,7 @@ import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; +import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAnonymousProvider; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnBasicAuthenticationProvider; @@ -17,9 +18,8 @@ import javasabr.mqtt.auth.service.config.annotation.ConditionalOnFileCredentialsSource; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; -import javasabr.rlib.collections.dictionary.DictionaryFactory; +import javasabr.rlib.collections.array.Array; import lombok.CustomLog; -import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -34,25 +34,13 @@ public class AuthenticationServiceSpringConfig { @Bean - AuthenticationService authenticationService( - List authenticationProviders, - @Value("${authentication.default-provider:#{null}}") @Nullable String defaultProviderName) { + AuthenticationService authenticationService(List providers) { log.info("Initializing AuthenticationService..."); - if (authenticationProviders.isEmpty()) { + if (providers.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - var providers = DictionaryFactory.mutableRefToRefDictionary(String.class, AuthenticationProvider.class); - authenticationProviders.forEach(value -> providers.put(value.getName(), value)); - AuthenticationProvider defaultProvider; - if (defaultProviderName == null) { - defaultProvider = authenticationProviders.getFirst(); - } else { - defaultProvider = providers.get(defaultProviderName); - } - if (defaultProvider == null) { - throw new AuthenticationConfigException("[%s] authenticator provider not found".formatted(defaultProviderName)); - } - return new DefaultAuthenticationService(providers.toReadOnly(), defaultProvider); + providers.sort(Comparator.comparingInt(provider -> provider.getAuthenticationType().priority())); + return new DefaultAuthenticationService(Array.copyOf(AuthenticationProvider.class, providers)); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index a1e4f1bf..bdd26c83 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -7,6 +7,5 @@ @ConfigurationProperties(prefix = "authentication") public record AuthenticationProperties( boolean allowAnonymous, - @Nullable String defaultProvider, @Nullable List providers, @Nullable List credentialsSources) {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 29769729..0408cb23 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -1,11 +1,11 @@ //file:noinspection SpringBootApplicationProperties package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.exception.AuthenticationConfigException import javasabr.mqtt.auth.credentials.source.FileCredentialsSource +import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.env.PropertiesPropertySourceLoader @@ -13,9 +13,10 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.core.env.PropertySource import org.springframework.core.io.ClassPathResource import org.springframework.test.context.TestPropertySource -import spock.lang.Ignore import spock.lang.Specification +import static javasabr.mqtt.auth.api.AuthenticationType.ANONYMOUS + class AuthenticationProviderTest extends IntegrationSpecification { @Autowired @@ -35,7 +36,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { (credentialsSource instanceof FileCredentialsSource) and: verifyEach(authenticationProviders) { provider -> - provider.name != "anonymous" + provider.authenticationType != ANONYMOUS !(provider instanceof AnonymousAuthenticationProvider) } } @@ -51,11 +52,11 @@ class AuthenticationProviderTest extends IntegrationSpecification { def "should create file credentials source and basic authentication provider"() { expect: authenticationProviders.any { provider -> - provider.name == "anonymous" && provider instanceof AnonymousAuthenticationProvider + provider.authenticationType == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider } and: authenticationProviders.any { provider -> - provider.name != "anonymous" && !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationType != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) } } } @@ -66,11 +67,11 @@ class AuthenticationProviderTest extends IntegrationSpecification { def "should create anonymous authentication provider"() { expect: authenticationProviders.any { provider -> - provider.name == "anonymous" && provider instanceof AnonymousAuthenticationProvider + provider.authenticationType == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider } and: !authenticationProviders.any { provider -> - provider.name != "anonymous" && !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationType != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) } } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index 13708db5..00ec8969 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -1,16 +1,17 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AnonymousAuthenticationProvider import javasabr.mqtt.auth.api.AuthRequest import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService +import javasabr.mqtt.auth.api.AuthenticationType import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource +import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource import org.springframework.test.context.TestPropertySource -import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.postgresql.PostgreSQLContainer import org.testcontainers.spock.Testcontainers import spock.lang.Shared @@ -64,7 +65,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { expect: credentialsSource instanceof DatabaseCredentialsSource verifyEach(authenticationProviders) { provider -> - provider.name != "anonymous" + provider.authenticationType != AuthenticationType.ANONYMOUS !(provider instanceof AnonymousAuthenticationProvider) } } From 6448b7b67b2e44284fbd6fab45e8b6c293423ff3 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 26 Dec 2025 00:45:21 +0100 Subject: [PATCH 045/107] [broker-141] Revert unnecessary changes --- .../application/ExternalConnectionTest.groovy | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy index 71384b82..77bf29d1 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ExternalConnectionTest.groovy @@ -121,4 +121,26 @@ class ExternalConnectionTest extends IntegrationSpecification { where: clientId << ["!@#!@*()^&"] } + + def "client should not connect to broker with wrong pass using mqtt 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + } + + def "client should not connect to broker with wrong pass using mqtt 5"() { + given: + def client = buildExternalMqtt5Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt5ConnAckException + cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } } From 165643845f088b2451f1c8aca9690535d93c2e85 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 26 Dec 2025 00:47:38 +0100 Subject: [PATCH 046/107] [broker-141] Rename AuthRequest to AuthenticationRequest --- .../src/main/java/javasabr/mqtt/auth/api/AuthRequest.java | 3 --- .../java/javasabr/mqtt/auth/api/AuthenticationRequest.java | 7 +++++++ .../java/javasabr/mqtt/auth/api/AuthenticationService.java | 2 +- .../mqtt/auth/service/DefaultAuthenticationService.java | 4 ++-- .../service/DatabaseAuthenticationServiceTest.groovy | 4 ++-- .../service/FileAuthenticationServiceTest.groovy | 4 ++-- .../handler/impl/ConnectInMqttInMessageHandler.java | 6 +++--- 7 files changed, 17 insertions(+), 13 deletions(-) delete mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java deleted file mode 100644 index b725f84e..00000000 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthRequest.java +++ /dev/null @@ -1,3 +0,0 @@ -package javasabr.mqtt.auth.api; - -public record AuthRequest(String username, byte[] password, String authenticationMethod, byte[] authenticationData) {} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java new file mode 100644 index 00000000..8fb9049a --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java @@ -0,0 +1,7 @@ +package javasabr.mqtt.auth.api; + +public record AuthenticationRequest( + String username, + byte[] password, + String authenticationMethod, + byte[] authenticationData) {} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java index ef879f7f..f67a71ee 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java @@ -3,5 +3,5 @@ import reactor.core.publisher.Mono; public interface AuthenticationService { - Mono authenticate(AuthRequest authRequest); + Mono authenticate(AuthenticationRequest authenticationRequest); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index b96c9832..2cb4b73d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service; -import javasabr.mqtt.auth.api.AuthRequest; +import javasabr.mqtt.auth.api.AuthenticationRequest; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.rlib.collections.array.Array; @@ -22,7 +22,7 @@ public DefaultAuthenticationService(Array providers) { } @Override - public Mono authenticate(AuthRequest request) { + public Mono authenticate(AuthenticationRequest request) { return Flux.fromIterable(providers) .concatMap(manager -> manager.authenticate( request.username(), diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index 00ec8969..a7f183a3 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AuthRequest +import javasabr.mqtt.auth.api.AuthenticationRequest import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService import javasabr.mqtt.auth.api.AuthenticationType @@ -48,7 +48,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new AuthRequest(userName, passwordBytes, "", new byte[0]) + def request = new AuthenticationRequest(userName, passwordBytes, "", new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy index 76bb9292..687af473 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AuthRequest +import javasabr.mqtt.auth.api.AuthenticationRequest import javasabr.mqtt.auth.api.AuthenticationService import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.TestPropertySource @@ -19,7 +19,7 @@ class FileAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new AuthRequest(userName, passwordBytes, "", new byte[0]) + def request = new AuthenticationRequest(userName, passwordBytes, "", new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 8885836c..ea610c0f 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,7 +11,7 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; -import javasabr.mqtt.auth.api.AuthRequest; +import javasabr.mqtt.auth.api.AuthenticationRequest; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttServerConnectionConfig; @@ -74,13 +74,13 @@ protected void processValidMessage( ExternalNetworkMqttUser user, ConnectMqttInMessage message) { resolveClientConnectionConfig(user, message); - AuthRequest authRequest = new AuthRequest( + AuthenticationRequest authenticationRequest = new AuthenticationRequest( message.username(), message.password(), message.authenticationMethod(), message.authenticationData()); authenticationService - .authenticate(authRequest) + .authenticate(authenticationRequest) .flatMap(ifTrue( user, message, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(user, connectAckReasonCode))) From 5fe633da515864460de332b6bc43fcfa1fab92a6 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:01:05 +0100 Subject: [PATCH 047/107] [broker-141] Pass authenticationMethod, improve logging --- .../javasabr/mqtt/auth/api/AuthenticationProvider.java | 2 +- .../mqtt/auth/provider/BasicAuthenticationProvider.java | 6 ++++-- .../auth/service/AnonymousAuthenticationProvider.java | 4 ++-- .../mqtt/auth/service/DefaultAuthenticationService.java | 9 ++++----- .../credentials/source/DatabaseCredentialsSource.java | 2 +- .../auth/credentials/source/FileCredentialsSource.java | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index 0cc4452f..3b409372 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -6,5 +6,5 @@ public interface AuthenticationProvider { AuthenticationType getAuthenticationType(); - Mono authenticate(String username, byte[] password, byte[] data); + Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data); } diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index de85134c..263a2a53 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -21,13 +21,15 @@ public AuthenticationType getAuthenticationType() { } @Override - public Mono authenticate(String username, byte[] password, byte[] data) { + public Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data) { return credentialsSource.isCredentialsExists(username, password); } @JsonValue @Override public String toString() { - return "{ \"credentialSource\": %s }".formatted(credentialsSource); + return "{ \"authenticationType\": \"%s\", \"credentialSource\": %s }".formatted( + getAuthenticationType(), + credentialsSource); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index 19c5b1c0..c6f180f3 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -13,13 +13,13 @@ public AuthenticationType getAuthenticationType() { } @Override - public Mono authenticate(String username, byte[] password, byte[] data) { + public Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data) { return Mono.just(StringUtils.isEmpty(username)); } @JsonValue @Override public String toString() { - return "true"; + return "{ \"authenticationType\": \"%s\", \"enabled\": true }".formatted(getAuthenticationType()); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 2cb4b73d..cf02e5ef 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -27,6 +27,7 @@ public Mono authenticate(AuthenticationRequest request) { .concatMap(manager -> manager.authenticate( request.username(), request.password(), + request.authenticationMethod(), request.authenticationData()) .onErrorReturn(false) ) @@ -37,18 +38,16 @@ private static String buildServiceDescription( Array providers) { var builder = new StringBuilder() - .append("{\n"); + .append("[\n"); for (AuthenticationProvider provider : providers) { builder - .append(" \"") - .append(provider.getAuthenticationType()) - .append("\": ") + .append(" ") .append(provider) .append(",\n"); } builder .delete(builder.length() - 2, builder.length()) - .append("\n}"); + .append("\n]"); return "Loaded total [%s] authentication providers: %s".formatted(providers.size(), builder); } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 0d822cd8..b5e7d3ee 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -43,6 +43,6 @@ public Mono isCredentialsExists(String userName, byte[] password) { @JsonValue @Override public String toString() { - return "{ \"%s\": \"%s\" }".formatted(getName(), dbUrl); + return "{ \"credentialsSource\": \"%s\", \"databaseUrl\": \"%s\" }".formatted(getName(), dbUrl); } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 99a8f7cc..6b0ac87c 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -37,6 +37,6 @@ public String getName() { @JsonValue @Override public String toString() { - return "{ \"%s\": \"%s\" }".formatted(getName(), fileName.getPath()); + return "{ \"credentialsSource\": \"%s\", \"filePath\": \"%s\" }".formatted(getName(), fileName.getPath()); } } From 4c709dba537ea1fd29e95a6146286c81bd4e2b7d Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:15:40 +0100 Subject: [PATCH 048/107] [broker-141] Add more authentication types --- .../javasabr/mqtt/auth/api/AuthenticationType.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java index f1ac4124..3151fa19 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java @@ -12,10 +12,13 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum AuthenticationType { X509(1), - ENHANCED(2), - JWT(3), - BASIC(4), - ANONYMOUS(5); + IP_CIDR(2), + ENHANCED(3), + JWT(4), + OAUTH(5), + BASIC(6), + LDAP(7), + ANONYMOUS(8); int priority; } From cdecb51c6f44ee9a35b067c45bdcc34d0d872599 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:24:01 +0100 Subject: [PATCH 049/107] [broker-141] Introduce DatasourceType --- .../mqtt/auth/api/CredentialsSource.java | 2 +- .../mqtt/auth/api/DatasourceType.java | 23 +++++++++++++++++++ .../source/DatabaseCredentialsSource.java | 5 ++-- .../source/FileCredentialsSource.java | 5 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java index 0f97edd8..32823a07 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -4,7 +4,7 @@ public interface CredentialsSource { - String getName(); + DatasourceType getName(); Mono isCredentialsExists(String userName, byte[] password); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java new file mode 100644 index 00000000..7c7ff466 --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java @@ -0,0 +1,23 @@ +package javasabr.mqtt.auth.api; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Getter +@Accessors +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum DatasourceType { + MEMORY(1), + REDIS(2), + DATABASE(3), + FILE(4), + LDAP(5), + HTTP(6), + SYSTEM(7); + + int priority; +} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index b5e7d3ee..552b2d8a 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialsSource; +import javasabr.mqtt.auth.api.DatasourceType; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -24,8 +25,8 @@ public class DatabaseCredentialsSource implements CredentialsSource { String dbUrl; @Override - public String getName() { - return "database"; + public DatasourceType getName() { + return DatasourceType.DATABASE; } @Override diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 6b0ac87c..64fd8de5 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -5,6 +5,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import javasabr.mqtt.auth.api.DatasourceType; import javasabr.mqtt.auth.api.InMemoryCredentialsSource; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; @@ -30,8 +31,8 @@ public void init() { } @Override - public String getName() { - return "file"; + public DatasourceType getName() { + return DatasourceType.FILE; } @JsonValue From 38132a89169ecd0fb2ef72635f573eacada53460 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:03:31 +0100 Subject: [PATCH 050/107] [broker-141] Refactoring --- .../test/resources/application-test.properties | 8 ++++---- .../mqtt/auth/api/AuthenticationProvider.java | 2 +- .../mqtt/auth/api/AuthenticationService.java | 2 +- .../mqtt/auth/api/CredentialsSource.java | 4 ++-- ...urceType.java => CredentialsSourceType.java} | 2 +- .../auth/api/InMemoryCredentialsSource.java | 4 ++-- ...icationRequest.java => MqttCredentials.java} | 2 +- .../provider/BasicAuthenticationProvider.java | 5 +++-- .../AnonymousAuthenticationProvider.java | 5 +++-- .../service/DefaultAuthenticationService.java | 12 +++--------- .../AuthenticationServiceSpringConfig.java | 7 +++++-- .../service/config/DatabaseSpringConfig.java | 16 ++++++++-------- .../property/DatabaseConnectionProperties.java | 8 ++++---- .../service/config/property/DatabaseDriver.java | 17 +++++++++++++++++ .../config/property/DatabaseUrlConfig.java | 8 ++++---- .../DatabaseAuthenticationServiceTest.groovy | 6 +++--- .../FileAuthenticationServiceTest.groovy | 4 ++-- .../test/resources/application-test.properties | 8 ++++---- buildSrc/src/main/groovy/configure-java.gradle | 2 +- .../impl/ConnectInMqttInMessageHandler.java | 6 +++--- .../source/DatabaseCredentialsSource.java | 15 ++++++++------- .../source/FileCredentialsSource.java | 10 ++++++---- 22 files changed, 86 insertions(+), 67 deletions(-) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{DatasourceType.java => CredentialsSourceType.java} (91%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{AuthenticationRequest.java => MqttCredentials.java} (79%) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 51aa8083..a7749d97 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -4,10 +4,10 @@ authentication.credentials-sources[0]=file credentials.source.file.name=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true -persistence.database.driver=postgresql -persistence.database.host=localhost -persistence.database.port=5432 -persistence.database.name=database-name +persistence.database.db-driver=postgresql +persistence.database.db-host=localhost +persistence.database.db-port=5432 +persistence.database.db-name=database-name persistence.database.max-idle-time=30m persistence.database.initial-pool-size=5 persistence.database.max-pool-size=10 diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index 3b409372..fecbe98e 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -6,5 +6,5 @@ public interface AuthenticationProvider { AuthenticationType getAuthenticationType(); - Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data); + Mono authenticate(MqttCredentials credentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java index f67a71ee..ac6978a2 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationService.java @@ -3,5 +3,5 @@ import reactor.core.publisher.Mono; public interface AuthenticationService { - Mono authenticate(AuthenticationRequest authenticationRequest); + Mono authenticate(MqttCredentials mqttCredentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java index 32823a07..64e23b0c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -4,7 +4,7 @@ public interface CredentialsSource { - DatasourceType getName(); + CredentialsSourceType getCredentialsSourceType(); - Mono isCredentialsExists(String userName, byte[] password); + Mono isCredentialsExists(MqttCredentials credentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java similarity index 91% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java index 7c7ff466..368936dd 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatasourceType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java @@ -10,7 +10,7 @@ @Accessors @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum DatasourceType { +public enum CredentialsSourceType { MEMORY(1), REDIS(2), DATABASE(3), diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java index 93f67ad8..12cbbf28 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java @@ -47,7 +47,7 @@ protected void reset(InputStream inStream) throws IOException { } @Override - public Mono isCredentialsExists(String userName, byte[] password) { - return Mono.just(Arrays.equals(password, credentials.get(userName))); + public Mono isCredentialsExists(MqttCredentials mqttCredentials) { + return Mono.just(Arrays.equals(mqttCredentials.password(), credentials.get(mqttCredentials.username()))); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java similarity index 79% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index 8fb9049a..f2d21826 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationRequest.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.api; -public record AuthenticationRequest( +public record MqttCredentials( String username, byte[] password, String authenticationMethod, diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 263a2a53..27e5c94c 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; @@ -21,8 +22,8 @@ public AuthenticationType getAuthenticationType() { } @Override - public Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data) { - return credentialsSource.isCredentialsExists(username, password); + public Mono authenticate(MqttCredentials credentials) { + return credentialsSource.isCredentialsExists(credentials); } @JsonValue diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index c6f180f3..0b7d3131 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; +import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.rlib.common.util.StringUtils; import reactor.core.publisher.Mono; @@ -13,8 +14,8 @@ public AuthenticationType getAuthenticationType() { } @Override - public Mono authenticate(String username, byte[] password, String authenticationMethod, byte[] data) { - return Mono.just(StringUtils.isEmpty(username)); + public Mono authenticate(MqttCredentials credentials) { + return Mono.just(StringUtils.isEmpty(credentials.username())); } @JsonValue diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index cf02e5ef..67169961 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service; -import javasabr.mqtt.auth.api.AuthenticationRequest; +import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.rlib.collections.array.Array; @@ -22,15 +22,9 @@ public DefaultAuthenticationService(Array providers) { } @Override - public Mono authenticate(AuthenticationRequest request) { + public Mono authenticate(MqttCredentials request) { return Flux.fromIterable(providers) - .concatMap(manager -> manager.authenticate( - request.username(), - request.password(), - request.authenticationMethod(), - request.authenticationData()) - .onErrorReturn(false) - ) + .concatMap(provider -> provider.authenticate(request).onErrorReturn(false)) .any(Boolean::booleanValue); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 40823ccf..5c855f75 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -19,6 +19,7 @@ import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayCollectors; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -39,8 +40,10 @@ AuthenticationService authenticationService(List provide if (providers.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - providers.sort(Comparator.comparingInt(provider -> provider.getAuthenticationType().priority())); - return new DefaultAuthenticationService(Array.copyOf(AuthenticationProvider.class, providers)); + Array prioritySortedAuthenticationProviders = providers.stream() + .sorted(Comparator.comparingInt(provider -> provider.getAuthenticationType().priority())) + .collect(ArrayCollectors.toArray(AuthenticationProvider.class)); + return new DefaultAuthenticationService(prioritySortedAuthenticationProviders); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 7fc74b7e..be9e3470 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -38,24 +38,24 @@ public class DatabaseSpringConfig { ConnectionFactoryOptions connectionFactoryOptions( DatabaseTimeoutsConfig databaseTimeoutsConfig, DatabaseUrlConfig databaseUrlConfig, - @Qualifier("readerCredentials") Credentials credentials){ + Credentials readerCredentials){ Map timeoutOptions = Map.of( "lock_timeout", databaseTimeoutsConfig.lockTimeout(), "statement_timeout", databaseTimeoutsConfig.statementTimeout()); return ConnectionFactoryOptions.builder() - .option(DATABASE, databaseUrlConfig.name()) - .option(DRIVER, databaseUrlConfig.driver()) - .option(HOST, databaseUrlConfig.host()) - .option(PORT, databaseUrlConfig.port()) - .option(USER, credentials.username()) - .option(PASSWORD, credentials.password()) + .option(DATABASE, databaseUrlConfig.dbName()) + .option(DRIVER, databaseUrlConfig.dbDriver().value()) + .option(HOST, databaseUrlConfig.dbHost()) + .option(PORT, databaseUrlConfig.dbPort()) + .option(USER, readerCredentials.username()) + .option(PASSWORD, readerCredentials.password()) .option(OPTIONS, timeoutOptions) .build(); } @Bean DatabaseUrlBuilder databaseUrlBuilder() { - return db -> "jdbc:%s://%s:%s/%s".formatted(db.driver(), db.host(), db.port(), db.name()); + return db -> "jdbc:%s://%s:%s/%s".formatted(db.dbDriver().value(), db.dbHost(), db.dbPort(), db.dbName()); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java index b1b31ec3..2fc550af 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java @@ -8,10 +8,10 @@ @ConfigurationProperties(prefix = "persistence.database") public record DatabaseConnectionProperties( Map users, - String driver, - String host, - int port, - String name, + DatabaseDriver dbDriver, + String dbHost, + int dbPort, + String dbName, String credentialsQuery, Duration maxIdleTime, int initialPoolSize, diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java new file mode 100644 index 00000000..59898b31 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.auth.service.config.property; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Getter +@Accessors +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum DatabaseDriver { + POSTGRESQL("postgresql"); + + String value; +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java index 5545e662..503c6d13 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java @@ -1,12 +1,12 @@ package javasabr.mqtt.auth.service.config.property; public interface DatabaseUrlConfig { - String driver(); + DatabaseDriver dbDriver(); - String host(); + String dbHost(); - int port(); + int dbPort(); - String name(); + String dbName(); } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index a7f183a3..449ff179 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AuthenticationRequest +import javasabr.mqtt.auth.api.MqttCredentials import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService import javasabr.mqtt.auth.api.AuthenticationType @@ -39,7 +39,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { postgreSQLContainer.start() - registry.add("persistence.database.port", { "${postgreSQLContainer.getMappedPort(5432)}" }) + registry.add("persistence.database.db-port", { "${postgreSQLContainer.getMappedPort(5432)}" }) } @Autowired @@ -48,7 +48,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new AuthenticationRequest(userName, passwordBytes, "", new byte[0]) + def request = new MqttCredentials(userName, passwordBytes, "", new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy index 687af473..1fe0f798 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AuthenticationRequest +import javasabr.mqtt.auth.api.MqttCredentials import javasabr.mqtt.auth.api.AuthenticationService import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.TestPropertySource @@ -19,7 +19,7 @@ class FileAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new AuthenticationRequest(userName, passwordBytes, "", new byte[0]) + def request = new MqttCredentials(userName, passwordBytes, "", new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index be5de1f2..6469d76e 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,8 +1,8 @@ credentials.source.file.name=classpath:credentials/test -persistence.database.driver=postgresql -persistence.database.host=localhost -persistence.database.port=5432 -persistence.database.name=testdb +persistence.database.db-driver=postgresql +persistence.database.db-host=localhost +persistence.database.db-port=5432 +persistence.database.db-name=testdb persistence.database.max-idle-time=30m persistence.database.initial-pool-size=5 persistence.database.max-pool-size=10 diff --git a/buildSrc/src/main/groovy/configure-java.gradle b/buildSrc/src/main/groovy/configure-java.gradle index 78f290de..3d99b1fb 100644 --- a/buildSrc/src/main/groovy/configure-java.gradle +++ b/buildSrc/src/main/groovy/configure-java.gradle @@ -31,9 +31,9 @@ tasks.withType(Test).configureEach { tasks.withType(JavaCompile).configureEach { options.compilerArgs += "--enable-preview" + options.compilerArgs += "-parameters" } - processResources { filter(ReplaceTokens, tokens: []) } \ No newline at end of file diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index ea610c0f..09ac5193 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,7 +11,7 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; -import javasabr.mqtt.auth.api.AuthenticationRequest; +import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttServerConnectionConfig; @@ -74,13 +74,13 @@ protected void processValidMessage( ExternalNetworkMqttUser user, ConnectMqttInMessage message) { resolveClientConnectionConfig(user, message); - AuthenticationRequest authenticationRequest = new AuthenticationRequest( + MqttCredentials mqttCredentials = new MqttCredentials( message.username(), message.password(), message.authenticationMethod(), message.authenticationData()); authenticationService - .authenticate(authenticationRequest) + .authenticate(mqttCredentials) .flatMap(ifTrue( user, message, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(user, connectAckReasonCode))) diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 552b2d8a..268b5627 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -3,7 +3,8 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialsSource; -import javasabr.mqtt.auth.api.DatasourceType; +import javasabr.mqtt.auth.api.CredentialsSourceType; +import javasabr.mqtt.auth.api.MqttCredentials; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -25,25 +26,25 @@ public class DatabaseCredentialsSource implements CredentialsSource { String dbUrl; @Override - public DatasourceType getName() { - return DatasourceType.DATABASE; + public CredentialsSourceType getCredentialsSourceType() { + return CredentialsSourceType.DATABASE; } @Override - public Mono isCredentialsExists(String userName, byte[] password) { + public Mono isCredentialsExists(MqttCredentials credentials) { return databaseClient .sql(CREDENTIALS_QUERY) - .bind("$1", userName) + .bind("$1", credentials.username()) .map(row -> row.get("password", byte[].class)) .all() .singleOrEmpty() - .map(existingPassword -> Arrays.equals(existingPassword, password)) + .map(existingPassword -> Arrays.equals(existingPassword, credentials.password())) .defaultIfEmpty(false); } @JsonValue @Override public String toString() { - return "{ \"credentialsSource\": \"%s\", \"databaseUrl\": \"%s\" }".formatted(getName(), dbUrl); + return "{ \"credentialsSource\": \"%s\", \"databaseUrl\": \"%s\" }".formatted(getCredentialsSourceType(), dbUrl); } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 64fd8de5..78a7a59d 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -5,7 +5,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import javasabr.mqtt.auth.api.DatasourceType; +import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.InMemoryCredentialsSource; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; @@ -31,13 +31,15 @@ public void init() { } @Override - public DatasourceType getName() { - return DatasourceType.FILE; + public CredentialsSourceType getCredentialsSourceType() { + return CredentialsSourceType.FILE; } @JsonValue @Override public String toString() { - return "{ \"credentialsSource\": \"%s\", \"filePath\": \"%s\" }".formatted(getName(), fileName.getPath()); + return "{ \"credentialsSource\": \"%s\", \"filePath\": \"%s\" }".formatted( + getCredentialsSourceType(), + fileName.getPath()); } } From 6824a232d2231c41fa8bee02cd4adde3d0bf3b8c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:04:39 +0100 Subject: [PATCH 051/107] [broker-141] Remove DatabaseUrlBuilder --- .../AuthenticationServiceSpringConfig.java | 8 ++------ .../service/config/DatabaseSpringConfig.java | 19 ++++++++----------- .../service/config/DatabaseUrlBuilder.java | 7 ------- .../source/DatabaseCredentialsSource.java | 4 ++-- 4 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 5c855f75..59ce122b 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -17,7 +17,6 @@ import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; import javasabr.mqtt.auth.service.config.annotation.ConditionalOnFileCredentialsSource; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayCollectors; import lombok.CustomLog; @@ -56,11 +55,8 @@ CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:c @Bean @ConditionalOnDatabaseCredentialsSource - CredentialsSource dbCredentialsSource( - DatabaseClient databaseClient, - DatabaseUrlConfig databaseUrlConfig, - DatabaseUrlBuilder databaseUrlBuilder) { - return new DatabaseCredentialsSource(databaseClient, databaseUrlBuilder.build(databaseUrlConfig)); + CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { + return new DatabaseCredentialsSource(databaseClient); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index be9e3470..4ab0c686 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -21,7 +21,6 @@ import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -53,11 +52,6 @@ ConnectionFactoryOptions connectionFactoryOptions( .build(); } - @Bean - DatabaseUrlBuilder databaseUrlBuilder() { - return db -> "jdbc:%s://%s:%s/%s".formatted(db.dbDriver().value(), db.dbHost(), db.dbPort(), db.dbName()); - } - @Bean DatabaseClient databaseClient(ConnectionFactory connectionFactory) { return DatabaseClient.create(connectionFactory); @@ -78,12 +72,15 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - Flyway flyway( - DatabaseUrlBuilder databaseUrlBuilder, - DatabaseUrlConfig databaseUrlConfig, - @Qualifier("adminCredentials") Credentials credentials) { + Flyway flyway(DatabaseUrlConfig databaseUrlConfig, Credentials adminCredentials) { + String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( + databaseUrlConfig.dbDriver().value(), + databaseUrlConfig.dbHost(), + databaseUrlConfig.dbPort(), + databaseUrlConfig.dbName()); return Flyway.configure() - .dataSource(databaseUrlBuilder.build(databaseUrlConfig), credentials.username(), credentials.password()).load(); + .dataSource(databaseUrl, adminCredentials.username(), adminCredentials.password()) + .load(); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java deleted file mode 100644 index 499aa56d..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUrlBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package javasabr.mqtt.auth.service.config; - -import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; - -public interface DatabaseUrlBuilder { - String build(DatabaseUrlConfig databaseUrlConfig); -} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 268b5627..0176a2e2 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -23,7 +23,6 @@ public class DatabaseCredentialsSource implements CredentialsSource { """; DatabaseClient databaseClient; - String dbUrl; @Override public CredentialsSourceType getCredentialsSourceType() { @@ -45,6 +44,7 @@ public Mono isCredentialsExists(MqttCredentials credentials) { @JsonValue @Override public String toString() { - return "{ \"credentialsSource\": \"%s\", \"databaseUrl\": \"%s\" }".formatted(getCredentialsSourceType(), dbUrl); + String dbDriver = databaseClient.getConnectionFactory().getMetadata().getName(); + return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getCredentialsSourceType(), dbDriver); } } From c92f47572bf9dca9b6e36c67295098373767920e Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 04:52:42 +0100 Subject: [PATCH 052/107] [broker-141] Rework authentication properties --- .../src/main/resources/application.properties | 1 - .../resources/application-test.properties | 21 ++--- .../mqtt/auth/api/AuthenticationType.java | 28 ++++--- .../mqtt/auth/api/MqttCredentials.java | 2 +- .../provider/BasicAuthenticationProvider.java | 6 +- .../AnonymousAuthenticationProvider.java | 6 +- .../service/DefaultAuthenticationService.java | 35 ++++++--- .../AuthenticationServiceSpringConfig.java | 59 +++++++++----- .../service/config/DatabaseSpringConfig.java | 78 +++++++++---------- .../ConditionalOnAnonymousProvider.java | 14 ---- .../ConditionalOnAuthenticationProvider.java | 17 ---- ...ditionalOnBasicAuthenticationProvider.java | 14 ---- .../ConditionalOnCredentialsSource.java | 17 ---- ...onditionalOnDatabaseCredentialsSource.java | 14 ---- .../ConditionalOnFileCredentialsSource.java | 14 ---- .../condition/AnonymousProviderCondition.java | 21 ----- .../AuthenticationConfigCondition.java | 37 --------- .../AuthenticationProviderCondition.java | 17 ---- .../condition/CredentialsSourceCondition.java | 17 ---- .../property/AuthenticationProperties.java | 8 +- .../AuthenticationProviderProperties.java | 8 ++ ...BasicAuthenticationProviderProperties.java | 8 ++ .../property/CredentialsSourceProperties.java | 17 ++++ .../DatabaseCredentialsSourceProperties.java | 12 +++ .../FileCredentialsSourceProperties.java | 6 ++ .../config/property/SwitchableProperty.java | 5 ++ .../service/AuthenticationProviderTest.groovy | 10 +-- .../DatabaseAuthenticationServiceTest.groovy | 10 ++- .../service/DatabaseTestSpringConfig.java | 19 +++++ .../FileAuthenticationServiceTest.groovy | 6 +- .../service/IntegrationSpecification.groovy | 5 +- .../resources/application-test.properties | 25 +++--- .../src/{main => test}/resources/log4j2.xml | 0 .../impl/ConnectInMqttInMessageHandler.java | 3 +- .../source/DatabaseCredentialsSource.java | 6 +- .../source/FileCredentialsSource.java | 6 +- 36 files changed, 257 insertions(+), 315 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java rename authentication-service/src/{main => test}/resources/log4j2.xml (100%) diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index 293f8cfc..89c783ad 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -1,2 +1 @@ authentication.allow-anonymous=false -credentials.source.file.name=credentials diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index a7749d97..9847f0f4 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,22 +1,11 @@ authentication.allow-anonymous=true -authentication.providers[0]=basic -authentication.credentials-sources[0]=file -credentials.source.file.name=classpath:auth/credentials-test +authentication.default-provider=basic +authentication.provider.basic.enabled=true +authentication.provider.basic.credentials-sources.file.enabled=true +authentication.provider.basic.credentials-sources.file.fs-path=classpath:auth/credentials-test + mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true -persistence.database.db-driver=postgresql -persistence.database.db-host=localhost -persistence.database.db-port=5432 -persistence.database.db-name=database-name -persistence.database.max-idle-time=30m -persistence.database.initial-pool-size=5 -persistence.database.max-pool-size=10 -persistence.database.lock-timeout=10s -persistence.database.statement-timeout=5m -persistence.database.users.admin.username=user -persistence.database.users.admin.password= -persistence.database.users.reader.username=user -persistence.database.users.reader.password= #acl.engine.type=groovy-dsl #acl.engine.groovy.dsl.config=classpath:test-acl.groovy diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java index 3151fa19..78e5c59c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java @@ -1,5 +1,9 @@ package javasabr.mqtt.auth.api; +import java.util.Arrays; +import java.util.function.Function; +import javasabr.rlib.collections.dictionary.DictionaryCollectors; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,14 +15,20 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum AuthenticationType { - X509(1), - IP_CIDR(2), - ENHANCED(3), - JWT(4), - OAUTH(5), - BASIC(6), - LDAP(7), - ANONYMOUS(8); + X509("x509"), + IP_CIDR("cidr"), + JWT("jwt"), + OAUTH2("oauth2"), + BASIC("basic"), + LDAP("ldap"), + ANONYMOUS("anon"); - int priority; + private static final RefToRefDictionary CACHE = Arrays.stream(values()) + .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationType::value, Function.identity())); + + String value; + + public static AuthenticationType fromValue(String value){ + return CACHE.get(value); + } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index f2d21826..4edf2033 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -3,5 +3,5 @@ public record MqttCredentials( String username, byte[] password, - String authenticationMethod, + AuthenticationType authenticationMethod, byte[] authenticationData) {} diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 27e5c94c..5abf5e56 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -26,11 +26,15 @@ public Mono authenticate(MqttCredentials credentials) { return credentialsSource.isCredentialsExists(credentials); } - @JsonValue @Override public String toString() { return "{ \"authenticationType\": \"%s\", \"credentialSource\": %s }".formatted( getAuthenticationType(), credentialsSource); } + + @JsonValue + public String jsonDebugValue() { + return toString(); + } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index 0b7d3131..7bb8294f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -18,9 +18,13 @@ public Mono authenticate(MqttCredentials credentials) { return Mono.just(StringUtils.isEmpty(credentials.username())); } - @JsonValue @Override public String toString() { return "{ \"authenticationType\": \"%s\", \"enabled\": true }".formatted(getAuthenticationType()); } + + @JsonValue + public String jsonDebugValue() { + return toString(); + } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 67169961..99b5de70 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,35 +1,50 @@ package javasabr.mqtt.auth.service; -import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.rlib.collections.array.Array; +import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.MqttCredentials; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; -import reactor.core.publisher.Flux; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; @CustomLog @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - Array providers; + RefToRefDictionary providers; + AuthenticationProvider defaultProvider; + @Nullable AnonymousAuthenticationProvider anonymousProvider; - public DefaultAuthenticationService(Array providers) { + public DefaultAuthenticationService( + RefToRefDictionary providers, + AuthenticationProvider defaultProvider, + AnonymousAuthenticationProvider anonymousAuthenticationProvider) { this.providers = providers; + this.defaultProvider = defaultProvider; + this.anonymousProvider = anonymousAuthenticationProvider; log.info(providers, DefaultAuthenticationService::buildServiceDescription); } @Override public Mono authenticate(MqttCredentials request) { - return Flux.fromIterable(providers) - .concatMap(provider -> provider.authenticate(request).onErrorReturn(false)) - .any(Boolean::booleanValue); + AuthenticationProvider primary = (request.authenticationMethod() == null) + ? defaultProvider + : providers.get(request.authenticationMethod()); + + Mono anonymousStep = + anonymousProvider != null ? anonymousProvider.authenticate(request).onErrorReturn(false) : Mono.just(false); + + return anonymousStep.flatMap(success -> (success || primary == null) + ? Mono.just(success) + : primary.authenticate(request).onErrorReturn(false)); } private static String buildServiceDescription( - Array providers) { + RefToRefDictionary providers) { var builder = new StringBuilder() .append("[\n"); @@ -43,6 +58,6 @@ private static String buildServiceDescription( .delete(builder.length() - 2, builder.length()) .append("\n]"); - return "Loaded total [%s] authentication providers: %s".formatted(providers.size(), builder); + return "Loaded total [%s] authentication provider: %s".formatted(providers.size(), builder); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 59ce122b..e96fcecb 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -1,10 +1,13 @@ package javasabr.mqtt.auth.service.config; +import static javasabr.rlib.collections.dictionary.DictionaryCollectors.toRefToRefDictionary; + import java.net.URI; -import java.util.Comparator; import java.util.List; +import java.util.function.Function; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; +import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; @@ -12,15 +15,14 @@ import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAnonymousProvider; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnBasicAuthenticationProvider; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnFileCredentialsSource; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.ArrayCollectors; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.CustomLog; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,43 +32,62 @@ @CustomLog @Configuration(proxyBeanMethods = false) @Import(DatabaseSpringConfig.class) -@EnableConfigurationProperties(AuthenticationProperties.class) +@EnableConfigurationProperties({ + AuthenticationProperties.class +}) public class AuthenticationServiceSpringConfig { @Bean - AuthenticationService authenticationService(List providers) { + AuthenticationService authenticationService( + List authenticationProviders, + AuthenticationProperties authenticationProperties, + @Autowired(required = false) AnonymousAuthenticationProvider anonymousAuthenticationProvider) { log.info("Initializing AuthenticationService..."); - if (providers.isEmpty()) { + if (authenticationProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - Array prioritySortedAuthenticationProviders = providers.stream() - .sorted(Comparator.comparingInt(provider -> provider.getAuthenticationType().priority())) - .collect(ArrayCollectors.toArray(AuthenticationProvider.class)); - return new DefaultAuthenticationService(prioritySortedAuthenticationProviders); + RefToRefDictionary providers = authenticationProviders.stream() + .collect(toRefToRefDictionary(AuthenticationProvider::getAuthenticationType, Function.identity())); + AuthenticationProvider defaultProvider; + AuthenticationType defaultAuthenticationType = authenticationProperties.defaultProvider(); + if (defaultAuthenticationType == null) { + defaultProvider = authenticationProviders.getFirst(); + } else { + defaultProvider = providers.get(defaultAuthenticationType); + } + if (defaultProvider == null) { + throw new AuthenticationConfigException("[%s] authenticator provider not found".formatted(defaultAuthenticationType)); + } + return new DefaultAuthenticationService(providers, defaultProvider, anonymousAuthenticationProvider); } @Bean - @ConditionalOnFileCredentialsSource - CredentialsSource fileCredentialsSource(@Value("${credentials.source.file.name:credentials}") URI fileName) { + @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") + @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.file.enabled", havingValue = "true") + CredentialsSource fileCredentialsSource(@Value("${authentication.provider.basic.credentials-sources.file.fs-path}") + URI fileName) { FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName); fileCredentialsSource.init(); return fileCredentialsSource; } @Bean - @ConditionalOnDatabaseCredentialsSource + @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") + @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.database.enabled", havingValue = "true") CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { return new DatabaseCredentialsSource(databaseClient); } @Bean - @ConditionalOnBasicAuthenticationProvider + @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") + @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") + @ConditionalOnBean(CredentialsSource.class) AuthenticationProvider basicAuthenticationProvider(CredentialsSource credentialsSource) { return new BasicAuthenticationProvider(credentialsSource); } @Bean - @ConditionalOnAnonymousProvider + @ConditionalOnProperty(name = "authentication.allow-anonymous", havingValue = "true") AuthenticationProvider anonymousAuthenticationProvider() { return new AnonymousAuthenticationProvider(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 4ab0c686..9c818fe3 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -14,12 +14,13 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnDatabaseCredentialsSource; +import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.CredentialsSourceType; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProviderProperties; import javasabr.mqtt.auth.service.config.property.Credentials; +import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; -import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; -import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; -import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -30,22 +31,25 @@ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(DatabaseConnectionProperties.class) -@ConditionalOnDatabaseCredentialsSource +@ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.database.enabled", havingValue = "true") public class DatabaseSpringConfig { @Bean ConnectionFactoryOptions connectionFactoryOptions( - DatabaseTimeoutsConfig databaseTimeoutsConfig, - DatabaseUrlConfig databaseUrlConfig, - Credentials readerCredentials){ + AuthenticationProperties authenticationProperties, + Credentials readerCredentials) { + AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() + .get(AuthenticationType.BASIC); + CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() + .get(CredentialsSourceType.DATABASE); Map timeoutOptions = Map.of( - "lock_timeout", databaseTimeoutsConfig.lockTimeout(), - "statement_timeout", databaseTimeoutsConfig.statementTimeout()); + "lock_timeout", credentialsSourceProperties.lockTimeout(), + "statement_timeout", credentialsSourceProperties.statementTimeout()); return ConnectionFactoryOptions.builder() - .option(DATABASE, databaseUrlConfig.dbName()) - .option(DRIVER, databaseUrlConfig.dbDriver().value()) - .option(HOST, databaseUrlConfig.dbHost()) - .option(PORT, databaseUrlConfig.dbPort()) + .option(DATABASE, credentialsSourceProperties.dbName()) + .option(DRIVER, credentialsSourceProperties.dbDriver().value()) + .option(HOST, credentialsSourceProperties.dbHost()) + .option(PORT, credentialsSourceProperties.dbPort()) .option(USER, readerCredentials.username()) .option(PASSWORD, readerCredentials.password()) .option(OPTIONS, timeoutOptions) @@ -60,44 +64,36 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - DatabasePoolConfig databasePoolConfig, + AuthenticationProperties authenticationProperties, ConnectionFactoryOptions connectionFactoryOptions) { + AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() + .get(AuthenticationType.BASIC); + CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() + .get(CredentialsSourceType.DATABASE); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(databasePoolConfig.maxIdleTime()) - .maxSize(databasePoolConfig.maxPoolSize()) - .initialSize(databasePoolConfig.initialPoolSize()) + .maxIdleTime(credentialsSourceProperties.maxIdleTime()) + .maxSize(credentialsSourceProperties.maxPoolSize()) + .initialSize(credentialsSourceProperties.initialPoolSize()) .build(); return new ConnectionPool(configuration); } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseUrlConfig databaseUrlConfig, Credentials adminCredentials) { + Flyway flyway(AuthenticationProperties authenticationProperties, Credentials adminCredentials) { + + AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() + .get(AuthenticationType.BASIC); + CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() + .get(CredentialsSourceType.DATABASE); + String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - databaseUrlConfig.dbDriver().value(), - databaseUrlConfig.dbHost(), - databaseUrlConfig.dbPort(), - databaseUrlConfig.dbName()); + credentialsSourceProperties.dbDriver().value(), + credentialsSourceProperties.dbHost(), + credentialsSourceProperties.dbPort(), + credentialsSourceProperties.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminCredentials.username(), adminCredentials.password()) .load(); } - - @Bean - @ConditionalOnProperty(name = "persistence.database.users.reader.username") - public Credentials readerCredentials(DatabaseUsersConfig usersConfig) { - return usersConfig.users().get("reader"); - } - - @Bean - @ConditionalOnProperty(name = "persistence.database.users.writer.username") - public Credentials writerCredentials(DatabaseUsersConfig usersConfig) { - return usersConfig.users().get("writer"); - } - - @Bean - @ConditionalOnProperty(name = "persistence.database.users.admin.username") - public Credentials adminCredentials(DatabaseUsersConfig usersConfig) { - return usersConfig.users().get("admin"); - } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java deleted file mode 100644 index 751eaee7..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAnonymousProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javasabr.mqtt.auth.service.config.condition.AnonymousProviderCondition; -import org.springframework.context.annotation.Conditional; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Conditional(AnonymousProviderCondition.class) -public @interface ConditionalOnAnonymousProvider { -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java deleted file mode 100644 index 503bce4c..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnAuthenticationProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javasabr.mqtt.auth.service.config.condition.AuthenticationProviderCondition; -import org.springframework.context.annotation.Conditional; - -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Conditional(AuthenticationProviderCondition.class) -public @interface ConditionalOnAuthenticationProvider { - String value(); - - String resource() default "Authentication Provider"; -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java deleted file mode 100644 index 708faaee..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnBasicAuthenticationProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") -@ConditionalOnAuthenticationProvider("basic") -public @interface ConditionalOnBasicAuthenticationProvider { -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java deleted file mode 100644 index 5c914eec..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnCredentialsSource.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javasabr.mqtt.auth.service.config.condition.CredentialsSourceCondition; -import org.springframework.context.annotation.Conditional; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Conditional(CredentialsSourceCondition.class) -public @interface ConditionalOnCredentialsSource { - String value(); - - String message() default "Credentials Source"; -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java deleted file mode 100644 index 9e7ba21c..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnDatabaseCredentialsSource.java +++ /dev/null @@ -1,14 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@ConditionalOnCredentialsSource("database") -@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") -public @interface ConditionalOnDatabaseCredentialsSource { -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java deleted file mode 100644 index 6890dff3..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/annotation/ConditionalOnFileCredentialsSource.java +++ /dev/null @@ -1,14 +0,0 @@ -package javasabr.mqtt.auth.service.config.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; - -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@ConditionalOnCredentialsSource("file") -@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") -public @interface ConditionalOnFileCredentialsSource { -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java deleted file mode 100644 index e3c7df01..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AnonymousProviderCondition.java +++ /dev/null @@ -1,21 +0,0 @@ -package javasabr.mqtt.auth.service.config.condition; - -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; - -public class AnonymousProviderCondition extends SpringBootCondition { - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - return Binder.get(context.getEnvironment()) - .bind("authentication", AuthenticationProperties.class) - .map(AuthenticationProperties::allowAnonymous) - .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled - ? ConditionOutcome.match("Anonymous connections allowed") - : ConditionOutcome.noMatch("Anonymous connections denied")) - .orElse(ConditionOutcome.noMatch("Authentication properties not defined")); - } -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java deleted file mode 100644 index 8fb705ae..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationConfigCondition.java +++ /dev/null @@ -1,37 +0,0 @@ -package javasabr.mqtt.auth.service.config.condition; - -import java.util.Map; -import java.util.Objects; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; - -public abstract class AuthenticationConfigCondition extends SpringBootCondition { - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Map attributes = metadata.getAnnotationAttributes(annotation().getName()); - attributes = Objects.requireNonNullElse(attributes, Map.of()); - String resourceId = attributes - .getOrDefault("value", "none") - .toString(); - String resource = attributes - .getOrDefault("resource", "unknown") - .toString(); - return Binder.get(context.getEnvironment()) - .bind("authentication", AuthenticationProperties.class) - .map(properties -> isEnabled(properties, resourceId)) - .map(isCredentialsSourceEnabled -> isCredentialsSourceEnabled - ? ConditionOutcome.match("%s '%s' enabled".formatted(resource, resourceId)) - : ConditionOutcome.noMatch("%s '%s' disabled".formatted(resource, resourceId))) - .orElse(ConditionOutcome.noMatch("Authentication properties not defined")); - - } - - abstract boolean isEnabled(AuthenticationProperties properties, String value); - - abstract Class annotation(); -} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java deleted file mode 100644 index 56b5a2d4..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/AuthenticationProviderCondition.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.auth.service.config.condition; - -import java.util.List; -import java.util.Objects; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnAuthenticationProvider; - -public class AuthenticationProviderCondition extends AuthenticationConfigCondition { - @Override - boolean isEnabled(AuthenticationProperties properties, String value) { - return Objects.requireNonNullElse(properties.providers(), List.of()).contains(value); - } - - Class annotation() { - return ConditionalOnAuthenticationProvider.class; - } -} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java deleted file mode 100644 index 787d5bf9..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/condition/CredentialsSourceCondition.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.auth.service.config.condition; - -import java.util.List; -import java.util.Objects; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.annotation.ConditionalOnCredentialsSource; - -public class CredentialsSourceCondition extends AuthenticationConfigCondition { - @Override - boolean isEnabled(AuthenticationProperties properties, String value) { - return Objects.requireNonNullElse(properties.credentialsSources(), List.of()).contains(value); - } - - Class annotation() { - return ConditionalOnCredentialsSource.class; - } -} \ No newline at end of file diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index bdd26c83..b428927e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -1,11 +1,11 @@ package javasabr.mqtt.auth.service.config.property; -import java.util.List; -import org.jspecify.annotations.Nullable; +import java.util.Map; +import javasabr.mqtt.auth.api.AuthenticationType; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") public record AuthenticationProperties( boolean allowAnonymous, - @Nullable List providers, - @Nullable List credentialsSources) {} + AuthenticationType defaultProvider, + Map provider) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java new file mode 100644 index 00000000..15b8e783 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java @@ -0,0 +1,8 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.util.Map; +import javasabr.mqtt.auth.api.CredentialsSourceType; + +public record AuthenticationProviderProperties( + boolean enabled, + Map credentialsSources) implements BasicAuthenticationProviderProperties{} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java new file mode 100644 index 00000000..2994f377 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java @@ -0,0 +1,8 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.util.Map; +import javasabr.mqtt.auth.api.CredentialsSourceType; + +public interface BasicAuthenticationProviderProperties extends SwitchableProperty{ + Map credentialsSources(); +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java new file mode 100644 index 00000000..bc23ed19 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.time.Duration; + +public record CredentialsSourceProperties( + boolean enabled, + String fsPath, + DatabaseDriver dbDriver, + String dbHost, + int dbPort, + String dbName, + Duration maxIdleTime, + int initialPoolSize, + int maxPoolSize, + String lockTimeout, + String statementTimeout +) implements FileCredentialsSourceProperties, DatabasePoolConfig, DatabaseUrlConfig, DatabaseTimeoutsConfig {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java new file mode 100644 index 00000000..b950e5bd --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.auth.service.config.property; + +import javasabr.mqtt.auth.service.config.DatabaseUsersConfig; + +public interface DatabaseCredentialsSourceProperties extends SwitchableProperty, DatabasePoolConfig, DatabaseUrlConfig, + DatabaseUsersConfig, DatabaseTimeoutsConfig { + + DatabaseDriver dbDriver(); + String dbHost(); + int dbPort(); + String dbName(); +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java new file mode 100644 index 00000000..d816a335 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.auth.service.config.property; + +public interface FileCredentialsSourceProperties extends SwitchableProperty{ + + String fsPath(); +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java new file mode 100644 index 00000000..17845366 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.auth.service.config.property; + +public interface SwitchableProperty { + boolean enabled(); +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 0408cb23..72ac218e 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -24,8 +24,8 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=false", - "authentication.providers[0]=basic", - "authentication.credentials-sources[0]=file" + "authentication.provider.basic.enabled=true", + "authentication.provider.basic.credentials-sources.file.enabled=true" ]) static class FileCredentialsSourceTest extends AuthenticationProviderTest { @Autowired @@ -44,8 +44,8 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=true", - "authentication.providers[0]=basic", - "authentication.credentials-sources[0]=file" + "authentication.provider.basic.enabled=true", + "authentication.provider.basic.credentials-sources.file.enabled=true" ]) static class AnonymousProviderTest extends AuthenticationProviderTest { @@ -108,7 +108,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { static Throwable rootCauseOf(Throwable throwable) { Throwable rootCause = throwable while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { - rootCause = rootCause.getCause(); + rootCause = rootCause.getCause() } return rootCause } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index 449ff179..4f3642ef 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -19,8 +19,8 @@ import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ "authentication.allow-anonymous=false", - "authentication.credentials-sources[0]=database", - "authentication.providers[0]=basic" + "authentication.provider.basic.enabled=true", + "authentication.provider.basic.credentials-sources.database.enabled=true" ]) @Testcontainers class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @@ -39,7 +39,9 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { postgreSQLContainer.start() - registry.add("persistence.database.db-port", { "${postgreSQLContainer.getMappedPort(5432)}" }) + registry.add( + "authentication.provider.basic.credentials-sources.database.db-port", + { "${postgreSQLContainer.getMappedPort(5432)}" }) } @Autowired @@ -48,7 +50,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new MqttCredentials(userName, passwordBytes, "", new byte[0]) + def request = new MqttCredentials(userName, passwordBytes, null, new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java new file mode 100644 index 00000000..35bcb83c --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -0,0 +1,19 @@ +package javasabr.mqtt.broker.application.service; + +import javasabr.mqtt.auth.service.config.property.Credentials; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DatabaseTestSpringConfig { + + @Bean + public Credentials readerCredentials() { + return new Credentials("user", ""); + } + + @Bean + public Credentials adminCredentials() { + return new Credentials("user", ""); + } +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy index 1fe0f798..52a6964d 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -8,8 +8,8 @@ import org.springframework.test.context.TestPropertySource import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ - "authentication.credentials-sources[0]=file", - "authentication.providers[0]=basic" + "authentication.provider.basic.credentials-sources.file.enabled=true", + "authentication.provider.basic.enabled=true" ]) class FileAuthenticationServiceTest extends IntegrationSpecification { @@ -19,7 +19,7 @@ class FileAuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according [credentials/test] file"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new MqttCredentials(userName, passwordBytes, "", new byte[0]) + def request = new MqttCredentials(userName, passwordBytes, null, new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy index 366b1109..4637be2f 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy @@ -6,6 +6,9 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import spock.lang.Specification @TestPropertySource("classpath:application-test.properties") -@SpringJUnitConfig(classes = AuthenticationServiceSpringConfig) +@SpringJUnitConfig(classes = [ + AuthenticationServiceSpringConfig, + DatabaseTestSpringConfig +]) class IntegrationSpecification extends Specification { } diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index 6469d76e..b627d53e 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,14 +1,11 @@ -credentials.source.file.name=classpath:credentials/test -persistence.database.db-driver=postgresql -persistence.database.db-host=localhost -persistence.database.db-port=5432 -persistence.database.db-name=testdb -persistence.database.max-idle-time=30m -persistence.database.initial-pool-size=5 -persistence.database.max-pool-size=10 -persistence.database.lock-timeout=10s -persistence.database.statement-timeout=5min -persistence.database.users.admin.username=user -persistence.database.users.admin.password= -persistence.database.users.reader.username=user -persistence.database.users.reader.password= +authentication.provider.basic.enabled=true +authentication.provider.basic.credentials-sources.file.fs-path=classpath:credentials/test +authentication.provider.basic.credentials-sources.database.db-driver=postgresql +authentication.provider.basic.credentials-sources.database.db-host=localhost +authentication.provider.basic.credentials-sources.database.db-port=5432 +authentication.provider.basic.credentials-sources.database.db-name=testdb +authentication.provider.basic.credentials-sources.database.max-idle-time=30m +authentication.provider.basic.credentials-sources.database.initial-pool-size=5 +authentication.provider.basic.credentials-sources.database.max-pool-size=10 +authentication.provider.basic.credentials-sources.database.lock-timeout=10s +authentication.provider.basic.credentials-sources.database.statement-timeout=5min diff --git a/authentication-service/src/main/resources/log4j2.xml b/authentication-service/src/test/resources/log4j2.xml similarity index 100% rename from authentication-service/src/main/resources/log4j2.xml rename to authentication-service/src/test/resources/log4j2.xml diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 09ac5193..f5ebbd96 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,6 +11,7 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; +import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; @@ -77,7 +78,7 @@ protected void processValidMessage( MqttCredentials mqttCredentials = new MqttCredentials( message.username(), message.password(), - message.authenticationMethod(), + AuthenticationType.fromValue(message.authenticationMethod()), message.authenticationData()); authenticationService .authenticate(mqttCredentials) diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 0176a2e2..b1d86006 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -41,10 +41,14 @@ public Mono isCredentialsExists(MqttCredentials credentials) { .defaultIfEmpty(false); } - @JsonValue @Override public String toString() { String dbDriver = databaseClient.getConnectionFactory().getMetadata().getName(); return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getCredentialsSourceType(), dbDriver); } + + @JsonValue + public String jsonDebugValue() { + return toString(); + } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 78a7a59d..1be9aa94 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -35,11 +35,15 @@ public CredentialsSourceType getCredentialsSourceType() { return CredentialsSourceType.FILE; } - @JsonValue @Override public String toString() { return "{ \"credentialsSource\": \"%s\", \"filePath\": \"%s\" }".formatted( getCredentialsSourceType(), fileName.getPath()); } + + @JsonValue + public String jsonDebugValue() { + return toString(); + } } From b5b5ae87d6c718a8567cf30d97dabe4028fee0b4 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 11:17:34 +0100 Subject: [PATCH 053/107] [broker-141] Rename DatabaseCredentials --- .../auth/service/config/DatabaseSpringConfig.java | 12 ++++++------ .../auth/service/config/DatabaseUsersConfig.java | 4 ++-- .../auth/service/config/property/Credentials.java | 3 --- .../property/DatabaseConnectionProperties.java | 2 +- .../service/config/property/DatabaseCredentials.java | 3 +++ .../service/DatabaseTestSpringConfig.java | 10 +++++----- 6 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java index 9c818fe3..325ad93d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java @@ -18,7 +18,7 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.AuthenticationProviderProperties; -import javasabr.mqtt.auth.service.config.property.Credentials; +import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; import org.flywaydb.core.Flyway; @@ -37,7 +37,7 @@ public class DatabaseSpringConfig { @Bean ConnectionFactoryOptions connectionFactoryOptions( AuthenticationProperties authenticationProperties, - Credentials readerCredentials) { + DatabaseCredentials readerDatabaseCredentials) { AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() .get(AuthenticationType.BASIC); CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() @@ -50,8 +50,8 @@ ConnectionFactoryOptions connectionFactoryOptions( .option(DRIVER, credentialsSourceProperties.dbDriver().value()) .option(HOST, credentialsSourceProperties.dbHost()) .option(PORT, credentialsSourceProperties.dbPort()) - .option(USER, readerCredentials.username()) - .option(PASSWORD, readerCredentials.password()) + .option(USER, readerDatabaseCredentials.username()) + .option(PASSWORD, readerDatabaseCredentials.password()) .option(OPTIONS, timeoutOptions) .build(); } @@ -80,7 +80,7 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - Flyway flyway(AuthenticationProperties authenticationProperties, Credentials adminCredentials) { + Flyway flyway(AuthenticationProperties authenticationProperties, DatabaseCredentials adminDatabaseCredentials) { AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() .get(AuthenticationType.BASIC); @@ -93,7 +93,7 @@ Flyway flyway(AuthenticationProperties authenticationProperties, Credentials adm credentialsSourceProperties.dbPort(), credentialsSourceProperties.dbName()); return Flyway.configure() - .dataSource(databaseUrl, adminCredentials.username(), adminCredentials.password()) + .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java index 3c1a1bed..05d3b13f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java @@ -1,10 +1,10 @@ package javasabr.mqtt.auth.service.config; import java.util.Map; -import javasabr.mqtt.auth.service.config.property.Credentials; +import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; public interface DatabaseUsersConfig { - Map users(); + Map users(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java deleted file mode 100644 index e763a7f1..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/Credentials.java +++ /dev/null @@ -1,3 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public record Credentials(String username, String password) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java index 2fc550af..0902e17a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java @@ -7,7 +7,7 @@ @ConfigurationProperties(prefix = "persistence.database") public record DatabaseConnectionProperties( - Map users, + Map users, DatabaseDriver dbDriver, String dbHost, int dbPort, diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java new file mode 100644 index 00000000..50d66c82 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java @@ -0,0 +1,3 @@ +package javasabr.mqtt.auth.service.config.property; + +public record DatabaseCredentials(String username, String password) {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index 35bcb83c..f3fc690a 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service; -import javasabr.mqtt.auth.service.config.property.Credentials; +import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -8,12 +8,12 @@ public class DatabaseTestSpringConfig { @Bean - public Credentials readerCredentials() { - return new Credentials("user", ""); + public DatabaseCredentials readerDatabaseCredentials() { + return new DatabaseCredentials("user", ""); } @Bean - public Credentials adminCredentials() { - return new Credentials("user", ""); + public DatabaseCredentials adminDatabaseCredentials() { + return new DatabaseCredentials("user", ""); } } From 3b71801736aab45dd24edce9070b29be2ff10514 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:19:19 +0100 Subject: [PATCH 054/107] [broker-141] Improve Spring configuration --- .../AuthenticationServiceSpringConfig.java | 6 +- ...atabaseCredentialsSourceSpringConfig.java} | 68 +++++++++---------- .../property/CredentialsSourceProperties.java | 2 +- .../DatabaseConnectionProperties.java | 21 ------ .../DatabaseCredentialsSourceProperties.java | 12 ---- .../service/config/property/FileConfig.java | 6 ++ .../FileCredentialsSourceProperties.java | 6 -- 7 files changed, 41 insertions(+), 80 deletions(-) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/{DatabaseSpringConfig.java => DatabaseCredentialsSourceSpringConfig.java} (55%) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index e96fcecb..4054e27e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -31,10 +31,8 @@ @CustomLog @Configuration(proxyBeanMethods = false) -@Import(DatabaseSpringConfig.class) -@EnableConfigurationProperties({ - AuthenticationProperties.class -}) +@Import(DatabaseCredentialsSourceSpringConfig.class) +@EnableConfigurationProperties(AuthenticationProperties.class) public class AuthenticationServiceSpringConfig { @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java similarity index 55% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 325ad93d..a1b9fd96 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -17,39 +17,45 @@ import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.AuthenticationProviderProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; +import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; +import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; +import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(DatabaseConnectionProperties.class) @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.database.enabled", havingValue = "true") -public class DatabaseSpringConfig { +@ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") +public class DatabaseCredentialsSourceSpringConfig { + + @Bean + public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { + return authenticationProperties + .provider() + .get(AuthenticationType.BASIC) + .credentialsSources() + .get(CredentialsSourceType.DATABASE); + } @Bean ConnectionFactoryOptions connectionFactoryOptions( - AuthenticationProperties authenticationProperties, + DatabaseUrlConfig databaseUrlConfig, + DatabaseTimeoutsConfig databaseTimeoutsConfig, DatabaseCredentials readerDatabaseCredentials) { - AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() - .get(AuthenticationType.BASIC); - CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() - .get(CredentialsSourceType.DATABASE); Map timeoutOptions = Map.of( - "lock_timeout", credentialsSourceProperties.lockTimeout(), - "statement_timeout", credentialsSourceProperties.statementTimeout()); + "lock_timeout", databaseTimeoutsConfig.lockTimeout(), + "statement_timeout", databaseTimeoutsConfig.statementTimeout()); return ConnectionFactoryOptions.builder() - .option(DATABASE, credentialsSourceProperties.dbName()) - .option(DRIVER, credentialsSourceProperties.dbDriver().value()) - .option(HOST, credentialsSourceProperties.dbHost()) - .option(PORT, credentialsSourceProperties.dbPort()) + .option(DATABASE, databaseUrlConfig.dbName()) + .option(DRIVER, databaseUrlConfig.dbDriver().value()) + .option(HOST, databaseUrlConfig.dbHost()) + .option(PORT, databaseUrlConfig.dbPort()) .option(USER, readerDatabaseCredentials.username()) .option(PASSWORD, readerDatabaseCredentials.password()) .option(OPTIONS, timeoutOptions) @@ -64,34 +70,24 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - AuthenticationProperties authenticationProperties, + DatabasePoolConfig databasePoolConfig, ConnectionFactoryOptions connectionFactoryOptions) { - AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() - .get(AuthenticationType.BASIC); - CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() - .get(CredentialsSourceType.DATABASE); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(credentialsSourceProperties.maxIdleTime()) - .maxSize(credentialsSourceProperties.maxPoolSize()) - .initialSize(credentialsSourceProperties.initialPoolSize()) + .maxIdleTime(databasePoolConfig.maxIdleTime()) + .maxSize(databasePoolConfig.maxPoolSize()) + .initialSize(databasePoolConfig.initialPoolSize()) .build(); return new ConnectionPool(configuration); } @Bean(initMethod = "migrate") - Flyway flyway(AuthenticationProperties authenticationProperties, DatabaseCredentials adminDatabaseCredentials) { - - AuthenticationProviderProperties authenticationProviderProperties = authenticationProperties.provider() - .get(AuthenticationType.BASIC); - CredentialsSourceProperties credentialsSourceProperties = authenticationProviderProperties.credentialsSources() - .get(CredentialsSourceType.DATABASE); - + Flyway flyway(DatabaseUrlConfig databaseUrlConfig, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - credentialsSourceProperties.dbDriver().value(), - credentialsSourceProperties.dbHost(), - credentialsSourceProperties.dbPort(), - credentialsSourceProperties.dbName()); + databaseUrlConfig.dbDriver().value(), + databaseUrlConfig.dbHost(), + databaseUrlConfig.dbPort(), + databaseUrlConfig.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java index bc23ed19..3ec09c4f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -14,4 +14,4 @@ public record CredentialsSourceProperties( int maxPoolSize, String lockTimeout, String statementTimeout -) implements FileCredentialsSourceProperties, DatabasePoolConfig, DatabaseUrlConfig, DatabaseTimeoutsConfig {} +) implements FileConfig, DatabasePoolConfig, DatabaseUrlConfig, DatabaseTimeoutsConfig {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java deleted file mode 100644 index 0902e17a..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.time.Duration; -import java.util.Map; -import javasabr.mqtt.auth.service.config.DatabaseUsersConfig; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "persistence.database") -public record DatabaseConnectionProperties( - Map users, - DatabaseDriver dbDriver, - String dbHost, - int dbPort, - String dbName, - String credentialsQuery, - Duration maxIdleTime, - int initialPoolSize, - int maxPoolSize, - String lockTimeout, - String statementTimeout -) implements DatabasePoolConfig, DatabaseUrlConfig, DatabaseUsersConfig, DatabaseTimeoutsConfig {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java deleted file mode 100644 index b950e5bd..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java +++ /dev/null @@ -1,12 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import javasabr.mqtt.auth.service.config.DatabaseUsersConfig; - -public interface DatabaseCredentialsSourceProperties extends SwitchableProperty, DatabasePoolConfig, DatabaseUrlConfig, - DatabaseUsersConfig, DatabaseTimeoutsConfig { - - DatabaseDriver dbDriver(); - String dbHost(); - int dbPort(); - String dbName(); -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java new file mode 100644 index 00000000..413ee482 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.auth.service.config.property; + +public interface FileConfig extends SwitchableProperty{ + + String fsPath(); +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java deleted file mode 100644 index d816a335..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public interface FileCredentialsSourceProperties extends SwitchableProperty{ - - String fsPath(); -} From 31079ab07ef516a30102ac85ebc6967e2a2b8e55 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:41:57 +0100 Subject: [PATCH 055/107] [broker-141] Improve Spring configuration --- .../AuthenticationServiceSpringConfig.java | 16 ++++++++++--- ...DatabaseCredentialsSourceSpringConfig.java | 23 ++++++++++--------- .../property/CredentialsSourceProperties.java | 3 ++- .../service/config/property/FileConfig.java | 4 +++- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 4054e27e..99ce2275 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -9,6 +9,7 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.AuthenticationType; import javasabr.mqtt.auth.api.CredentialsSource; +import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; @@ -16,6 +17,7 @@ import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; @@ -59,12 +61,20 @@ AuthenticationService authenticationService( return new DefaultAuthenticationService(providers, defaultProvider, anonymousAuthenticationProvider); } + @Bean + public CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { + return authenticationProperties + .provider() + .get(AuthenticationType.BASIC) + .credentialsSources() + .get(CredentialsSourceType.FILE); + } + @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.file.enabled", havingValue = "true") - CredentialsSource fileCredentialsSource(@Value("${authentication.provider.basic.credentials-sources.file.fs-path}") - URI fileName) { - FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileName); + CredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { + FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileCredentialsSourceProperties.fsPath()); fileCredentialsSource.init(); return fileCredentialsSource; } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index a1b9fd96..6955f11e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -23,6 +23,7 @@ import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -45,8 +46,8 @@ public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationP @Bean ConnectionFactoryOptions connectionFactoryOptions( - DatabaseUrlConfig databaseUrlConfig, - DatabaseTimeoutsConfig databaseTimeoutsConfig, + @Qualifier("dbCredentialsSourceProperties") DatabaseUrlConfig databaseUrlConfig, + @Qualifier("dbCredentialsSourceProperties") DatabaseTimeoutsConfig databaseTimeoutsConfig, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( "lock_timeout", databaseTimeoutsConfig.lockTimeout(), @@ -70,24 +71,24 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - DatabasePoolConfig databasePoolConfig, + DatabasePoolConfig dbCredentialsSourceProperties, ConnectionFactoryOptions connectionFactoryOptions) { ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(databasePoolConfig.maxIdleTime()) - .maxSize(databasePoolConfig.maxPoolSize()) - .initialSize(databasePoolConfig.initialPoolSize()) + .maxIdleTime(dbCredentialsSourceProperties.maxIdleTime()) + .maxSize(dbCredentialsSourceProperties.maxPoolSize()) + .initialSize(dbCredentialsSourceProperties.initialPoolSize()) .build(); return new ConnectionPool(configuration); } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseUrlConfig databaseUrlConfig, DatabaseCredentials adminDatabaseCredentials) { + Flyway flyway(DatabaseUrlConfig dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - databaseUrlConfig.dbDriver().value(), - databaseUrlConfig.dbHost(), - databaseUrlConfig.dbPort(), - databaseUrlConfig.dbName()); + dbCredentialsSourceProperties.dbDriver().value(), + dbCredentialsSourceProperties.dbHost(), + dbCredentialsSourceProperties.dbPort(), + dbCredentialsSourceProperties.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java index 3ec09c4f..ff9e76ee 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -1,10 +1,11 @@ package javasabr.mqtt.auth.service.config.property; +import java.net.URI; import java.time.Duration; public record CredentialsSourceProperties( boolean enabled, - String fsPath, + URI fsPath, DatabaseDriver dbDriver, String dbHost, int dbPort, diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java index 413ee482..a06e8fc2 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java @@ -1,6 +1,8 @@ package javasabr.mqtt.auth.service.config.property; +import java.net.URI; + public interface FileConfig extends SwitchableProperty{ - String fsPath(); + URI fsPath(); } From 5dab30af8e78486e2eeb802bf6ea1aa0241366fb Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:50:44 +0100 Subject: [PATCH 056/107] [broker-141] Rename members --- .../resources/application-test.properties | 8 ++--- ...ionType.java => AuthenticationMethod.java} | 8 ++--- .../mqtt/auth/api/AuthenticationProvider.java | 2 +- .../mqtt/auth/api/MqttCredentials.java | 2 +- .../provider/BasicAuthenticationProvider.java | 10 +++---- .../AnonymousAuthenticationProvider.java | 8 ++--- .../service/DefaultAuthenticationService.java | 10 +++---- .../AuthenticationServiceSpringConfig.java | 29 +++++++++---------- ...DatabaseCredentialsSourceSpringConfig.java | 12 ++++---- .../service/config/DatabaseUsersConfig.java | 10 ------- .../property/AuthenticationProperties.java | 6 ++-- .../AuthenticationProviderProperties.java | 4 +-- ...BasicAuthenticationProviderProperties.java | 4 +-- .../service/config/property/FileConfig.java | 2 +- .../config/property/SwitchableProperty.java | 5 ---- .../service/AuthenticationProviderTest.groovy | 20 ++++++------- .../DatabaseAuthenticationServiceTest.groovy | 10 +++---- .../FileAuthenticationServiceTest.groovy | 4 +-- .../resources/application-test.properties | 22 +++++++------- .../impl/ConnectInMqttInMessageHandler.java | 4 +-- 20 files changed, 82 insertions(+), 98 deletions(-) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{AuthenticationType.java => AuthenticationMethod.java} (81%) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 2bf9f1f3..8a239fb4 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,8 +1,8 @@ authentication.allow-anonymous=true -authentication.default-provider=basic -authentication.provider.basic.enabled=true -authentication.provider.basic.credentials-sources.file.enabled=true -authentication.provider.basic.credentials-sources.file.fs-path=classpath:auth/credentials-test +authentication.default-method=basic +authentication.method.basic.enabled=true +authentication.method.basic.credentials-source.file.enabled=true +authentication.method.basic.credentials-source.file.fs-path=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java similarity index 81% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index 78e5c59c..4cdc0800 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -14,7 +14,7 @@ @Accessors @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum AuthenticationType { +public enum AuthenticationMethod { X509("x509"), IP_CIDR("cidr"), JWT("jwt"), @@ -23,12 +23,12 @@ public enum AuthenticationType { LDAP("ldap"), ANONYMOUS("anon"); - private static final RefToRefDictionary CACHE = Arrays.stream(values()) - .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationType::value, Function.identity())); + private static final RefToRefDictionary CACHE = Arrays.stream(values()) + .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationMethod::value, Function.identity())); String value; - public static AuthenticationType fromValue(String value){ + public static AuthenticationMethod fromValue(String value){ return CACHE.get(value); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index fecbe98e..7950b703 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -4,7 +4,7 @@ public interface AuthenticationProvider { - AuthenticationType getAuthenticationType(); + AuthenticationMethod getAuthenticationMethod(); Mono authenticate(MqttCredentials credentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index 4edf2033..c238b190 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -3,5 +3,5 @@ public record MqttCredentials( String username, byte[] password, - AuthenticationType authenticationMethod, + AuthenticationMethod authenticationMethod, byte[] authenticationData) {} diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 5abf5e56..8beb621e 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.MqttCredentials; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.CredentialsSource; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -17,8 +17,8 @@ public class BasicAuthenticationProvider implements AuthenticationProvider { CredentialsSource credentialsSource; @Override - public AuthenticationType getAuthenticationType() { - return AuthenticationType.BASIC; + public AuthenticationMethod getAuthenticationMethod() { + return AuthenticationMethod.BASIC; } @Override @@ -28,8 +28,8 @@ public Mono authenticate(MqttCredentials credentials) { @Override public String toString() { - return "{ \"authenticationType\": \"%s\", \"credentialSource\": %s }".formatted( - getAuthenticationType(), + return "{ \"authenticationMethod\": \"%s\", \"credentialSource\": %s }".formatted( + getAuthenticationMethod(), credentialsSource); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index 7bb8294f..cfd2a520 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -3,14 +3,14 @@ import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.MqttCredentials; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.rlib.common.util.StringUtils; import reactor.core.publisher.Mono; public class AnonymousAuthenticationProvider implements AuthenticationProvider { @Override - public AuthenticationType getAuthenticationType() { - return AuthenticationType.ANONYMOUS; + public AuthenticationMethod getAuthenticationMethod() { + return AuthenticationMethod.ANONYMOUS; } @Override @@ -20,7 +20,7 @@ public Mono authenticate(MqttCredentials credentials) { @Override public String toString() { - return "{ \"authenticationType\": \"%s\", \"enabled\": true }".formatted(getAuthenticationType()); + return "{ \"authenticationMethod\": \"%s\", \"enabled\": true }".formatted(getAuthenticationMethod()); } @JsonValue diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 99b5de70..8734c1cf 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -2,7 +2,7 @@ import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.AccessLevel; @@ -15,12 +15,12 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - RefToRefDictionary providers; + RefToRefDictionary providers; AuthenticationProvider defaultProvider; @Nullable AnonymousAuthenticationProvider anonymousProvider; public DefaultAuthenticationService( - RefToRefDictionary providers, + RefToRefDictionary providers, AuthenticationProvider defaultProvider, AnonymousAuthenticationProvider anonymousAuthenticationProvider) { this.providers = providers; @@ -44,7 +44,7 @@ public Mono authenticate(MqttCredentials request) { } private static String buildServiceDescription( - RefToRefDictionary providers) { + RefToRefDictionary providers) { var builder = new StringBuilder() .append("[\n"); @@ -58,6 +58,6 @@ private static String buildServiceDescription( .delete(builder.length() - 2, builder.length()) .append("\n]"); - return "Loaded total [%s] authentication provider: %s".formatted(providers.size(), builder); + return "Loaded total [%s] authentication method: %s".formatted(providers.size(), builder); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 99ce2275..d2353354 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -2,12 +2,11 @@ import static javasabr.rlib.collections.dictionary.DictionaryCollectors.toRefToRefDictionary; -import java.net.URI; import java.util.List; import java.util.function.Function; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; @@ -21,7 +20,6 @@ import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -46,17 +44,18 @@ AuthenticationService authenticationService( if (authenticationProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - RefToRefDictionary providers = authenticationProviders.stream() - .collect(toRefToRefDictionary(AuthenticationProvider::getAuthenticationType, Function.identity())); + RefToRefDictionary providers = authenticationProviders.stream() + .collect(toRefToRefDictionary(AuthenticationProvider::getAuthenticationMethod, Function.identity())); AuthenticationProvider defaultProvider; - AuthenticationType defaultAuthenticationType = authenticationProperties.defaultProvider(); - if (defaultAuthenticationType == null) { + AuthenticationMethod defaultAuthenticationMethod = authenticationProperties.defaultMethod(); + if (defaultAuthenticationMethod == null) { defaultProvider = authenticationProviders.getFirst(); } else { - defaultProvider = providers.get(defaultAuthenticationType); + defaultProvider = providers.get(defaultAuthenticationMethod); } if (defaultProvider == null) { - throw new AuthenticationConfigException("[%s] authenticator provider not found".formatted(defaultAuthenticationType)); + throw new AuthenticationConfigException("[%s] authenticator method not found".formatted( + defaultAuthenticationMethod)); } return new DefaultAuthenticationService(providers, defaultProvider, anonymousAuthenticationProvider); } @@ -64,15 +63,15 @@ AuthenticationService authenticationService( @Bean public CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { return authenticationProperties - .provider() - .get(AuthenticationType.BASIC) - .credentialsSources() + .method() + .get(AuthenticationMethod.BASIC) + .credentialsSource() .get(CredentialsSourceType.FILE); } @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.file.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.file.enabled", havingValue = "true") CredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileCredentialsSourceProperties.fsPath()); fileCredentialsSource.init(); @@ -81,14 +80,14 @@ CredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredenti @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") - @ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.database.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.database.enabled", havingValue = "true") CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { return new DatabaseCredentialsSource(databaseClient); } @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") - @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") @ConditionalOnBean(CredentialsSource.class) AuthenticationProvider basicAuthenticationProvider(CredentialsSource credentialsSource) { return new BasicAuthenticationProvider(credentialsSource); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 6955f11e..184fefdc 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -14,7 +14,7 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; @@ -31,16 +31,16 @@ import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "authentication.provider.basic.credentials-sources.database.enabled", havingValue = "true") -@ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") +@ConditionalOnProperty(name = "authentication.method.basic.credentials-source.database.enabled", havingValue = "true") +@ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") public class DatabaseCredentialsSourceSpringConfig { @Bean public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { return authenticationProperties - .provider() - .get(AuthenticationType.BASIC) - .credentialsSources() + .method() + .get(AuthenticationMethod.BASIC) + .credentialsSource() .get(CredentialsSourceType.DATABASE); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java deleted file mode 100644 index 05d3b13f..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseUsersConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package javasabr.mqtt.auth.service.config; - -import java.util.Map; -import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; - -public interface DatabaseUsersConfig { - - Map users(); -} - diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index b428927e..a7a02cc9 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -1,11 +1,11 @@ package javasabr.mqtt.auth.service.config.property; import java.util.Map; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") public record AuthenticationProperties( boolean allowAnonymous, - AuthenticationType defaultProvider, - Map provider) {} + AuthenticationMethod defaultMethod, + Map method) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java index 15b8e783..71932ae2 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java @@ -4,5 +4,5 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; public record AuthenticationProviderProperties( - boolean enabled, - Map credentialsSources) implements BasicAuthenticationProviderProperties{} + boolean enabled, Map credentialsSource) implements + BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java index 2994f377..4316c470 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java @@ -3,6 +3,6 @@ import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSourceType; -public interface BasicAuthenticationProviderProperties extends SwitchableProperty{ - Map credentialsSources(); +public interface BasicAuthenticationProviderProperties { + Map credentialsSource(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java index a06e8fc2..ec4a1e62 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java @@ -2,7 +2,7 @@ import java.net.URI; -public interface FileConfig extends SwitchableProperty{ +public interface FileConfig { URI fsPath(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java deleted file mode 100644 index 17845366..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SwitchableProperty.java +++ /dev/null @@ -1,5 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public interface SwitchableProperty { - boolean enabled(); -} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 72ac218e..5871d9bf 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -15,7 +15,7 @@ import org.springframework.core.io.ClassPathResource import org.springframework.test.context.TestPropertySource import spock.lang.Specification -import static javasabr.mqtt.auth.api.AuthenticationType.ANONYMOUS +import static javasabr.mqtt.auth.api.AuthenticationMethod.ANONYMOUS class AuthenticationProviderTest extends IntegrationSpecification { @@ -24,8 +24,8 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=false", - "authentication.provider.basic.enabled=true", - "authentication.provider.basic.credentials-sources.file.enabled=true" + "authentication.method.basic.enabled=true", + "authentication.method.basic.credentials-source.file.enabled=true" ]) static class FileCredentialsSourceTest extends AuthenticationProviderTest { @Autowired @@ -36,7 +36,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { (credentialsSource instanceof FileCredentialsSource) and: verifyEach(authenticationProviders) { provider -> - provider.authenticationType != ANONYMOUS + provider.authenticationMethod != ANONYMOUS !(provider instanceof AnonymousAuthenticationProvider) } } @@ -44,19 +44,19 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=true", - "authentication.provider.basic.enabled=true", - "authentication.provider.basic.credentials-sources.file.enabled=true" + "authentication.method.basic.enabled=true", + "authentication.method.basic.credentials-source.file.enabled=true" ]) static class AnonymousProviderTest extends AuthenticationProviderTest { def "should create file credentials source and basic authentication provider"() { expect: authenticationProviders.any { provider -> - provider.authenticationType == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider + provider.authenticationMethod == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider } and: authenticationProviders.any { provider -> - provider.authenticationType != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationMethod != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) } } } @@ -67,11 +67,11 @@ class AuthenticationProviderTest extends IntegrationSpecification { def "should create anonymous authentication provider"() { expect: authenticationProviders.any { provider -> - provider.authenticationType == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider + provider.authenticationMethod == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider } and: !authenticationProviders.any { provider -> - provider.authenticationType != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationMethod != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) } } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index 4f3642ef..538833fd 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -3,7 +3,7 @@ package javasabr.mqtt.broker.application.service import javasabr.mqtt.auth.api.MqttCredentials import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService -import javasabr.mqtt.auth.api.AuthenticationType +import javasabr.mqtt.auth.api.AuthenticationMethod import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider @@ -19,8 +19,8 @@ import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ "authentication.allow-anonymous=false", - "authentication.provider.basic.enabled=true", - "authentication.provider.basic.credentials-sources.database.enabled=true" + "authentication.method.basic.enabled=true", + "authentication.method.basic.credentials-source.database.enabled=true" ]) @Testcontainers class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @@ -40,7 +40,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { static void configureProperties(DynamicPropertyRegistry registry) { postgreSQLContainer.start() registry.add( - "authentication.provider.basic.credentials-sources.database.db-port", + "authentication.method.basic.credentials-source.database.db-port", { "${postgreSQLContainer.getMappedPort(5432)}" }) } @@ -67,7 +67,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { expect: credentialsSource instanceof DatabaseCredentialsSource verifyEach(authenticationProviders) { provider -> - provider.authenticationType != AuthenticationType.ANONYMOUS + provider.authenticationMethod != AuthenticationMethod.ANONYMOUS !(provider instanceof AnonymousAuthenticationProvider) } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy index 52a6964d..bae98b0e 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -8,8 +8,8 @@ import org.springframework.test.context.TestPropertySource import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ - "authentication.provider.basic.credentials-sources.file.enabled=true", - "authentication.provider.basic.enabled=true" + "authentication.method.basic.credentials-source.file.enabled=true", + "authentication.method.basic.enabled=true" ]) class FileAuthenticationServiceTest extends IntegrationSpecification { diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index b627d53e..f11987f9 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,11 +1,11 @@ -authentication.provider.basic.enabled=true -authentication.provider.basic.credentials-sources.file.fs-path=classpath:credentials/test -authentication.provider.basic.credentials-sources.database.db-driver=postgresql -authentication.provider.basic.credentials-sources.database.db-host=localhost -authentication.provider.basic.credentials-sources.database.db-port=5432 -authentication.provider.basic.credentials-sources.database.db-name=testdb -authentication.provider.basic.credentials-sources.database.max-idle-time=30m -authentication.provider.basic.credentials-sources.database.initial-pool-size=5 -authentication.provider.basic.credentials-sources.database.max-pool-size=10 -authentication.provider.basic.credentials-sources.database.lock-timeout=10s -authentication.provider.basic.credentials-sources.database.statement-timeout=5min +authentication.method.basic.enabled=true +authentication.method.basic.credentials-source.file.fs-path=classpath:credentials/test +authentication.method.basic.credentials-source.database.db-driver=postgresql +authentication.method.basic.credentials-source.database.db-host=localhost +authentication.method.basic.credentials-source.database.db-port=5432 +authentication.method.basic.credentials-source.database.db-name=testdb +authentication.method.basic.credentials-source.database.max-idle-time=30m +authentication.method.basic.credentials-source.database.initial-pool-size=5 +authentication.method.basic.credentials-source.database.max-pool-size=10 +authentication.method.basic.credentials-source.database.lock-timeout=10s +authentication.method.basic.credentials-source.database.statement-timeout=5min diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index f5ebbd96..63055bfc 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -11,7 +11,7 @@ import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; -import javasabr.mqtt.auth.api.AuthenticationType; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.model.MqttClientConnectionConfig; @@ -78,7 +78,7 @@ protected void processValidMessage( MqttCredentials mqttCredentials = new MqttCredentials( message.username(), message.password(), - AuthenticationType.fromValue(message.authenticationMethod()), + AuthenticationMethod.fromValue(message.authenticationMethod()), message.authenticationData()); authenticationService .authenticate(mqttCredentials) From cf0faec1b0a93f06d5cd11844e10821c97f91814 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:23:15 +0100 Subject: [PATCH 057/107] [broker-141] Improve DefaultAuthenticationService --- .../provider/BasicAuthenticationProvider.java | 5 --- .../AnonymousAuthenticationProvider.java | 8 +---- .../service/DefaultAuthenticationService.java | 31 ++++++++++--------- .../source/DatabaseCredentialsSource.java | 5 --- .../source/FileCredentialsSource.java | 5 --- 5 files changed, 17 insertions(+), 37 deletions(-) diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 8beb621e..0142df28 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -32,9 +32,4 @@ public String toString() { getAuthenticationMethod(), credentialsSource); } - - @JsonValue - public String jsonDebugValue() { - return toString(); - } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index cfd2a520..7d544337 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -1,9 +1,8 @@ package javasabr.mqtt.auth.service; -import com.fasterxml.jackson.annotation.JsonValue; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.MqttCredentials; -import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.rlib.common.util.StringUtils; import reactor.core.publisher.Mono; @@ -22,9 +21,4 @@ public Mono authenticate(MqttCredentials credentials) { public String toString() { return "{ \"authenticationMethod\": \"%s\", \"enabled\": true }".formatted(getAuthenticationMethod()); } - - @JsonValue - public String jsonDebugValue() { - return toString(); - } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 8734c1cf..0d5ab080 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -15,32 +15,33 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - RefToRefDictionary providers; + RefToRefDictionary availableProviders; AuthenticationProvider defaultProvider; @Nullable AnonymousAuthenticationProvider anonymousProvider; public DefaultAuthenticationService( - RefToRefDictionary providers, + RefToRefDictionary availableProviders, AuthenticationProvider defaultProvider, - AnonymousAuthenticationProvider anonymousAuthenticationProvider) { - this.providers = providers; + @Nullable AnonymousAuthenticationProvider anonymousAuthenticationProvider) { + this.availableProviders = availableProviders; this.defaultProvider = defaultProvider; this.anonymousProvider = anonymousAuthenticationProvider; - log.info(providers, DefaultAuthenticationService::buildServiceDescription); + log.info(availableProviders, DefaultAuthenticationService::buildServiceDescription); } @Override public Mono authenticate(MqttCredentials request) { - AuthenticationProvider primary = (request.authenticationMethod() == null) - ? defaultProvider - : providers.get(request.authenticationMethod()); - - Mono anonymousStep = - anonymousProvider != null ? anonymousProvider.authenticate(request).onErrorReturn(false) : Mono.just(false); - - return anonymousStep.flatMap(success -> (success || primary == null) - ? Mono.just(success) - : primary.authenticate(request).onErrorReturn(false)); + AuthenticationMethod authenticationMethod = request.authenticationMethod(); + AuthenticationProvider targetProvider = + authenticationMethod == null ? defaultProvider : availableProviders.get(authenticationMethod); + return Mono.justOrEmpty(anonymousProvider) + .flatMap(provider -> provider.authenticate(request)) + .onErrorReturn(false) + .filter(Boolean::booleanValue) + .switchIfEmpty(Mono.justOrEmpty(targetProvider) + .flatMap(provider -> provider.authenticate(request)) + .onErrorReturn(false) + .defaultIfEmpty(false)); } private static String buildServiceDescription( diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index b1d86006..c5428d7e 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -46,9 +46,4 @@ public String toString() { String dbDriver = databaseClient.getConnectionFactory().getMetadata().getName(); return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getCredentialsSourceType(), dbDriver); } - - @JsonValue - public String jsonDebugValue() { - return toString(); - } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 1be9aa94..50f8c90f 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -41,9 +41,4 @@ public String toString() { getCredentialsSourceType(), fileName.getPath()); } - - @JsonValue - public String jsonDebugValue() { - return toString(); - } } From baf885de56bab2fbb0e98773f39d102909e6010d Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:19:00 +0100 Subject: [PATCH 058/107] [broker-141] Improve DefaultAuthenticationService creation --- .../service/DefaultAuthenticationService.java | 41 +++++++++++++------ .../AuthenticationServiceSpringConfig.java | 36 ++++------------ 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 0d5ab080..5403cae8 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,10 +1,17 @@ package javasabr.mqtt.auth.service; +import static java.util.stream.Collectors.toMap; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.MqttCredentials; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; @@ -15,18 +22,27 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - RefToRefDictionary availableProviders; + Map availableProviders; AuthenticationProvider defaultProvider; @Nullable AnonymousAuthenticationProvider anonymousProvider; public DefaultAuthenticationService( - RefToRefDictionary availableProviders, - AuthenticationProvider defaultProvider, - @Nullable AnonymousAuthenticationProvider anonymousAuthenticationProvider) { - this.availableProviders = availableProviders; - this.defaultProvider = defaultProvider; - this.anonymousProvider = anonymousAuthenticationProvider; - log.info(availableProviders, DefaultAuthenticationService::buildServiceDescription); + List configuredProviders, + @Nullable AuthenticationMethod defaultMethod, + @Nullable AnonymousAuthenticationProvider anonymousProvider) { + if (configuredProviders.isEmpty()) { + throw new AuthenticationConfigException("Authenticator providers are not configured"); + } + this.availableProviders = new EnumMap<>(configuredProviders.stream() + .collect(toMap(AuthenticationProvider::getAuthenticationMethod, Function.identity()))); + this.defaultProvider = Optional.ofNullable(defaultMethod) + .map(this.availableProviders::get) + .orElseGet(configuredProviders::getFirst); + if (defaultProvider == null) { + throw new AuthenticationConfigException("[%s] method not found".formatted(defaultMethod)); + } + this.anonymousProvider = anonymousProvider; + log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); } @Override @@ -44,12 +60,11 @@ public Mono authenticate(MqttCredentials request) { .defaultIfEmpty(false)); } - private static String buildServiceDescription( - RefToRefDictionary providers) { + private static String buildServiceDescription(Map providers) { var builder = new StringBuilder() .append("[\n"); - for (AuthenticationProvider provider : providers) { + for (AuthenticationProvider provider : providers.values()) { builder .append(" ") .append(provider) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index d2353354..198c4bfe 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -1,15 +1,11 @@ package javasabr.mqtt.auth.service.config; -import static javasabr.rlib.collections.dictionary.DictionaryCollectors.toRefToRefDictionary; - import java.util.List; -import java.util.function.Function; +import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; -import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; @@ -17,7 +13,6 @@ import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -37,27 +32,12 @@ public class AuthenticationServiceSpringConfig { @Bean AuthenticationService authenticationService( - List authenticationProviders, + List availableProviders, AuthenticationProperties authenticationProperties, - @Autowired(required = false) AnonymousAuthenticationProvider anonymousAuthenticationProvider) { + @Autowired(required = false) AnonymousAuthenticationProvider anonymousProvider) { log.info("Initializing AuthenticationService..."); - if (authenticationProviders.isEmpty()) { - throw new AuthenticationConfigException("Authenticator providers are not configured"); - } - RefToRefDictionary providers = authenticationProviders.stream() - .collect(toRefToRefDictionary(AuthenticationProvider::getAuthenticationMethod, Function.identity())); - AuthenticationProvider defaultProvider; AuthenticationMethod defaultAuthenticationMethod = authenticationProperties.defaultMethod(); - if (defaultAuthenticationMethod == null) { - defaultProvider = authenticationProviders.getFirst(); - } else { - defaultProvider = providers.get(defaultAuthenticationMethod); - } - if (defaultProvider == null) { - throw new AuthenticationConfigException("[%s] authenticator method not found".formatted( - defaultAuthenticationMethod)); - } - return new DefaultAuthenticationService(providers, defaultProvider, anonymousAuthenticationProvider); + return new DefaultAuthenticationService(availableProviders, defaultAuthenticationMethod, anonymousProvider); } @Bean @@ -69,13 +49,11 @@ public CredentialsSourceProperties fileCredentialsSourceProperties(Authenticatio .get(CredentialsSourceType.FILE); } - @Bean + @Bean(initMethod = "init") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.file.enabled", havingValue = "true") - CredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { - FileCredentialsSource fileCredentialsSource = new FileCredentialsSource(fileCredentialsSourceProperties.fsPath()); - fileCredentialsSource.init(); - return fileCredentialsSource; + FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { + return new FileCredentialsSource(fileCredentialsSourceProperties.fsPath()); } @Bean From bcc2d33a7ffc26b385cfb2e9ba3bb7f2a88b7436 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:42:20 +0100 Subject: [PATCH 059/107] [broker-141] Refactoring --- .../resources/application-test.properties | 2 +- .../AuthenticationServiceSpringConfig.java | 2 +- ...DatabaseCredentialsSourceSpringConfig.java | 21 ++++++++----------- .../AuthenticationProviderProperties.java | 5 +++-- .../property/CredentialsSourceProperties.java | 4 ++-- ...fig.java => DatabaseConnectionConfig.java} | 2 +- .../service/config/property/FileConfig.java | 2 +- .../resources/application-test.properties | 2 +- .../src/main/groovy/configure-java.gradle | 8 ++++--- 9 files changed, 24 insertions(+), 24 deletions(-) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{DatabaseUrlConfig.java => DatabaseConnectionConfig.java} (76%) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 8a239fb4..dfd03a02 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -2,7 +2,7 @@ authentication.allow-anonymous=true authentication.default-method=basic authentication.method.basic.enabled=true authentication.method.basic.credentials-source.file.enabled=true -authentication.method.basic.credentials-source.file.fs-path=classpath:auth/credentials-test +authentication.method.basic.credentials-source.file.uri-path=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 198c4bfe..429ba71d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -53,7 +53,7 @@ public CredentialsSourceProperties fileCredentialsSourceProperties(Authenticatio @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.file.enabled", havingValue = "true") FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { - return new FileCredentialsSource(fileCredentialsSourceProperties.fsPath()); + return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 184fefdc..18a32bd8 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -18,12 +18,10 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseConnectionConfig; import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; -import javasabr.mqtt.auth.service.config.property.DatabaseTimeoutsConfig; -import javasabr.mqtt.auth.service.config.property.DatabaseUrlConfig; import org.flywaydb.core.Flyway; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,17 +44,16 @@ public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationP @Bean ConnectionFactoryOptions connectionFactoryOptions( - @Qualifier("dbCredentialsSourceProperties") DatabaseUrlConfig databaseUrlConfig, - @Qualifier("dbCredentialsSourceProperties") DatabaseTimeoutsConfig databaseTimeoutsConfig, + CredentialsSourceProperties dbCredentialsSourceProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( - "lock_timeout", databaseTimeoutsConfig.lockTimeout(), - "statement_timeout", databaseTimeoutsConfig.statementTimeout()); + "lock_timeout", dbCredentialsSourceProperties.lockTimeout(), + "statement_timeout", dbCredentialsSourceProperties.statementTimeout()); return ConnectionFactoryOptions.builder() - .option(DATABASE, databaseUrlConfig.dbName()) - .option(DRIVER, databaseUrlConfig.dbDriver().value()) - .option(HOST, databaseUrlConfig.dbHost()) - .option(PORT, databaseUrlConfig.dbPort()) + .option(DATABASE, dbCredentialsSourceProperties.dbName()) + .option(DRIVER, dbCredentialsSourceProperties.dbDriver().value()) + .option(HOST, dbCredentialsSourceProperties.dbHost()) + .option(PORT, dbCredentialsSourceProperties.dbPort()) .option(USER, readerDatabaseCredentials.username()) .option(PASSWORD, readerDatabaseCredentials.password()) .option(OPTIONS, timeoutOptions) @@ -83,7 +80,7 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseUrlConfig dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { + Flyway flyway(DatabaseConnectionConfig dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( dbCredentialsSourceProperties.dbDriver().value(), dbCredentialsSourceProperties.dbHost(), diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java index 71932ae2..adce8d98 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java @@ -4,5 +4,6 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; public record AuthenticationProviderProperties( - boolean enabled, Map credentialsSource) implements - BasicAuthenticationProviderProperties {} + boolean enabled, + Map credentialsSource) implements BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java index ff9e76ee..5450e1bb 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -5,7 +5,7 @@ public record CredentialsSourceProperties( boolean enabled, - URI fsPath, + URI uriPath, DatabaseDriver dbDriver, String dbHost, int dbPort, @@ -15,4 +15,4 @@ public record CredentialsSourceProperties( int maxPoolSize, String lockTimeout, String statementTimeout -) implements FileConfig, DatabasePoolConfig, DatabaseUrlConfig, DatabaseTimeoutsConfig {} +) implements FileConfig, DatabasePoolConfig, DatabaseConnectionConfig, DatabaseTimeoutsConfig {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java similarity index 76% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java index 503c6d13..38778c4a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseUrlConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service.config.property; -public interface DatabaseUrlConfig { +public interface DatabaseConnectionConfig { DatabaseDriver dbDriver(); String dbHost(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java index ec4a1e62..87346f88 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java @@ -4,5 +4,5 @@ public interface FileConfig { - URI fsPath(); + URI uriPath(); } diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index f11987f9..e163d9ef 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ authentication.method.basic.enabled=true -authentication.method.basic.credentials-source.file.fs-path=classpath:credentials/test +authentication.method.basic.credentials-source.file.uri-path=classpath:credentials/test authentication.method.basic.credentials-source.database.db-driver=postgresql authentication.method.basic.credentials-source.database.db-host=localhost authentication.method.basic.credentials-source.database.db-port=5432 diff --git a/buildSrc/src/main/groovy/configure-java.gradle b/buildSrc/src/main/groovy/configure-java.gradle index 3d99b1fb..b620138b 100644 --- a/buildSrc/src/main/groovy/configure-java.gradle +++ b/buildSrc/src/main/groovy/configure-java.gradle @@ -30,10 +30,12 @@ tasks.withType(Test).configureEach { } tasks.withType(JavaCompile).configureEach { - options.compilerArgs += "--enable-preview" - options.compilerArgs += "-parameters" + options.compilerArgs.addAll([ + '--enable-preview', + '-parameters' + ]) } processResources { filter(ReplaceTokens, tokens: []) -} \ No newline at end of file +} From c91a1f5ae67efd22e55a2df6a8d1bc4ae71f26a3 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:59:07 +0100 Subject: [PATCH 060/107] [broker-141] Introduce DatabaseConfig --- .../service/config/DatabaseCredentialsSourceSpringConfig.java | 3 ++- .../service/config/property/CredentialsSourceProperties.java | 3 +-- .../mqtt/auth/service/config/property/DatabaseConfig.java | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 18a32bd8..06f67a59 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -18,6 +18,7 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseConfig; import javasabr.mqtt.auth.service.config.property.DatabaseConnectionConfig; import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; @@ -44,7 +45,7 @@ public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationP @Bean ConnectionFactoryOptions connectionFactoryOptions( - CredentialsSourceProperties dbCredentialsSourceProperties, + DatabaseConfig dbCredentialsSourceProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( "lock_timeout", dbCredentialsSourceProperties.lockTimeout(), diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java index 5450e1bb..d0b5415a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -14,5 +14,4 @@ public record CredentialsSourceProperties( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout -) implements FileConfig, DatabasePoolConfig, DatabaseConnectionConfig, DatabaseTimeoutsConfig {} + String statementTimeout) implements FileConfig, DatabaseConfig {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java new file mode 100644 index 00000000..d2b858ef --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java @@ -0,0 +1,4 @@ +package javasabr.mqtt.auth.service.config.property; + +public interface DatabaseConfig extends DatabasePoolConfig, DatabaseConnectionConfig, DatabaseTimeoutsConfig {} + From 8327533207fd22c65f9548d407fa3a79c1ca3c43 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:46:45 +0100 Subject: [PATCH 061/107] [broker-141] Rework properties structure --- .../resources/application-test.properties | 5 +++-- .../AuthenticationServiceSpringConfig.java | 6 ++---- ...DatabaseCredentialsSourceSpringConfig.java | 5 +---- .../property/AuthenticationProperties.java | 4 +++- .../AuthenticationProviderProperties.java | 8 +------- ...BasicAuthenticationProviderProperties.java | 7 +------ .../service/AuthenticationProviderTest.groovy | 4 ++-- .../DatabaseAuthenticationServiceTest.groovy | 4 ++-- .../FileAuthenticationServiceTest.groovy | 2 +- .../resources/application-test.properties | 20 +++++++++---------- 10 files changed, 26 insertions(+), 39 deletions(-) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index dfd03a02..dcdf128c 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,8 +1,9 @@ authentication.allow-anonymous=true authentication.default-method=basic authentication.method.basic.enabled=true -authentication.method.basic.credentials-source.file.enabled=true -authentication.method.basic.credentials-source.file.uri-path=classpath:auth/credentials-test +authentication.method.basic.credentials-sources=file +authentication.credentials-source.file.enabled=true +authentication.credentials-source.file.uri-path=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 429ba71d..14da32fe 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -43,22 +43,20 @@ AuthenticationService authenticationService( @Bean public CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { return authenticationProperties - .method() - .get(AuthenticationMethod.BASIC) .credentialsSource() .get(CredentialsSourceType.FILE); } @Bean(initMethod = "init") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.file.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); } @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") - @ConditionalOnProperty(name = "authentication.method.basic.credentials-source.database.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { return new DatabaseCredentialsSource(databaseClient); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 06f67a59..acf3ae83 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -14,7 +14,6 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; @@ -30,15 +29,13 @@ import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "authentication.method.basic.credentials-source.database.enabled", havingValue = "true") +@ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") public class DatabaseCredentialsSourceSpringConfig { @Bean public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { return authenticationProperties - .method() - .get(AuthenticationMethod.BASIC) .credentialsSource() .get(CredentialsSourceType.DATABASE); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index a7a02cc9..01bd6929 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -2,10 +2,12 @@ import java.util.Map; import javasabr.mqtt.auth.api.AuthenticationMethod; +import javasabr.mqtt.auth.api.CredentialsSourceType; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") public record AuthenticationProperties( boolean allowAnonymous, AuthenticationMethod defaultMethod, - Map method) {} + Map method, + Map credentialsSource) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java index adce8d98..ac08e918 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java @@ -1,9 +1,3 @@ package javasabr.mqtt.auth.service.config.property; -import java.util.Map; -import javasabr.mqtt.auth.api.CredentialsSourceType; - -public record AuthenticationProviderProperties( - boolean enabled, - Map credentialsSource) implements BasicAuthenticationProviderProperties {} +public record AuthenticationProviderProperties(boolean enabled) implements BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java index 4316c470..74b1e8fa 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java @@ -1,8 +1,3 @@ package javasabr.mqtt.auth.service.config.property; -import java.util.Map; -import javasabr.mqtt.auth.api.CredentialsSourceType; - -public interface BasicAuthenticationProviderProperties { - Map credentialsSource(); -} +public interface BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 5871d9bf..b085fd70 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -25,7 +25,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=false", "authentication.method.basic.enabled=true", - "authentication.method.basic.credentials-source.file.enabled=true" + "authentication.credentials-source.file.enabled=true" ]) static class FileCredentialsSourceTest extends AuthenticationProviderTest { @Autowired @@ -45,7 +45,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { @TestPropertySource(properties = [ "authentication.allow-anonymous=true", "authentication.method.basic.enabled=true", - "authentication.method.basic.credentials-source.file.enabled=true" + "authentication.credentials-source.file.enabled=true" ]) static class AnonymousProviderTest extends AuthenticationProviderTest { diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy index 538833fd..b1de1d3d 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ "authentication.allow-anonymous=false", "authentication.method.basic.enabled=true", - "authentication.method.basic.credentials-source.database.enabled=true" + "authentication.credentials-source.database.enabled=true" ]) @Testcontainers class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @@ -40,7 +40,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { static void configureProperties(DynamicPropertyRegistry registry) { postgreSQLContainer.start() registry.add( - "authentication.method.basic.credentials-source.database.db-port", + "authentication.credentials-source.database.db-port", { "${postgreSQLContainer.getMappedPort(5432)}" }) } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy index bae98b0e..552bf323 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy @@ -8,7 +8,7 @@ import org.springframework.test.context.TestPropertySource import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ - "authentication.method.basic.credentials-source.file.enabled=true", + "authentication.credentials-source.file.enabled=true", "authentication.method.basic.enabled=true" ]) class FileAuthenticationServiceTest extends IntegrationSpecification { diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index e163d9ef..34d228d6 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,11 +1,11 @@ authentication.method.basic.enabled=true -authentication.method.basic.credentials-source.file.uri-path=classpath:credentials/test -authentication.method.basic.credentials-source.database.db-driver=postgresql -authentication.method.basic.credentials-source.database.db-host=localhost -authentication.method.basic.credentials-source.database.db-port=5432 -authentication.method.basic.credentials-source.database.db-name=testdb -authentication.method.basic.credentials-source.database.max-idle-time=30m -authentication.method.basic.credentials-source.database.initial-pool-size=5 -authentication.method.basic.credentials-source.database.max-pool-size=10 -authentication.method.basic.credentials-source.database.lock-timeout=10s -authentication.method.basic.credentials-source.database.statement-timeout=5min +authentication.credentials-source.file.uri-path=classpath:credentials/test +authentication.credentials-source.database.db-driver=postgresql +authentication.credentials-source.database.db-host=localhost +authentication.credentials-source.database.db-port=5432 +authentication.credentials-source.database.db-name=testdb +authentication.credentials-source.database.max-idle-time=30m +authentication.credentials-source.database.initial-pool-size=5 +authentication.credentials-source.database.max-pool-size=10 +authentication.credentials-source.database.lock-timeout=10s +authentication.credentials-source.database.statement-timeout=5min From 5faaff08a5bc62da9e24c0d42d52a8c853aeefc5 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:55:40 +0100 Subject: [PATCH 062/107] [broker-141] Support a list of credentials sources --- .../mqtt/auth/api/CredentialsSourceType.java | 9 +++-- .../provider/BasicAuthenticationProvider.java | 29 +++++++++++----- .../AuthenticationServiceSpringConfig.java | 19 +++-------- ...DatabaseCredentialsSourceSpringConfig.java | 14 +++++--- .../service/AuthenticationProviderTest.groovy | 14 ++++---- ...roovy => AuthenticationServiceTest.groovy} | 22 ++++++++---- .../FileAuthenticationServiceTest.groovy | 34 ------------------- 7 files changed, 59 insertions(+), 82 deletions(-) rename authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/{DatabaseAuthenticationServiceTest.groovy => AuthenticationServiceTest.groovy} (79%) delete mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java index 368936dd..1aff7cf7 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java @@ -11,13 +11,12 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum CredentialsSourceType { - MEMORY(1), + FILE(1), REDIS(2), DATABASE(3), - FILE(4), - LDAP(5), - HTTP(6), - SYSTEM(7); + LDAP(4), + HTTP(5), + SYSTEM(6); int priority; } diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 0142df28..4a02cf34 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -1,20 +1,30 @@ package javasabr.mqtt.auth.provider; -import com.fasterxml.jackson.annotation.JsonValue; -import javasabr.mqtt.auth.api.AuthenticationProvider; -import javasabr.mqtt.auth.api.MqttCredentials; +import static java.util.Comparator.comparingInt; + +import java.util.List; import javasabr.mqtt.auth.api.AuthenticationMethod; +import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.CredentialsSource; +import javasabr.mqtt.auth.api.MqttCredentials; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayCollectors; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -@RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class BasicAuthenticationProvider implements AuthenticationProvider { - CredentialsSource credentialsSource; + Array credentialsSources; + + public BasicAuthenticationProvider(List credentialsSources) { + this.credentialsSources = credentialsSources.stream() + .sorted(comparingInt(credentialsSource -> credentialsSource.getCredentialsSourceType().priority())) + .collect(ArrayCollectors.toArray(CredentialsSource.class)); + } @Override public AuthenticationMethod getAuthenticationMethod() { @@ -23,13 +33,14 @@ public AuthenticationMethod getAuthenticationMethod() { @Override public Mono authenticate(MqttCredentials credentials) { - return credentialsSource.isCredentialsExists(credentials); + return Flux.fromIterable(credentialsSources) + .concatMap(credentialsSource -> credentialsSource.isCredentialsExists(credentials)) + .any(Boolean::booleanValue); } @Override public String toString() { return "{ \"authenticationMethod\": \"%s\", \"credentialSource\": %s }".formatted( - getAuthenticationMethod(), - credentialsSource); + getAuthenticationMethod(), credentialsSources); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 14da32fe..c5abb39d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -6,7 +6,6 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; @@ -22,7 +21,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.r2dbc.core.DatabaseClient; @CustomLog @Configuration(proxyBeanMethods = false) @@ -41,10 +39,8 @@ AuthenticationService authenticationService( } @Bean - public CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { - return authenticationProperties - .credentialsSource() - .get(CredentialsSourceType.FILE); + CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { + return authenticationProperties.credentialsSource().get(CredentialsSourceType.FILE); } @Bean(initMethod = "init") @@ -54,19 +50,12 @@ FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCred return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); } - @Bean - @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") - @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") - CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { - return new DatabaseCredentialsSource(databaseClient); - } - @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") @ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") @ConditionalOnBean(CredentialsSource.class) - AuthenticationProvider basicAuthenticationProvider(CredentialsSource credentialsSource) { - return new BasicAuthenticationProvider(credentialsSource); + AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { + return new BasicAuthenticationProvider(configuredCredentialsSources); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index acf3ae83..015573c9 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -14,7 +14,9 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; +import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; import javasabr.mqtt.auth.service.config.property.DatabaseConfig; @@ -22,6 +24,7 @@ import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; import org.flywaydb.core.Flyway; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,14 +33,17 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") -@ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") +@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") public class DatabaseCredentialsSourceSpringConfig { + @Bean + CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { + return new DatabaseCredentialsSource(databaseClient); + } + @Bean public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { - return authenticationProperties - .credentialsSource() - .get(CredentialsSourceType.DATABASE); + return authenticationProperties.credentialsSource().get(CredentialsSourceType.DATABASE); } @Bean diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index b085fd70..d14bd89b 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -5,6 +5,7 @@ import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.exception.AuthenticationConfigException import javasabr.mqtt.auth.credentials.source.FileCredentialsSource +import javasabr.mqtt.auth.provider.BasicAuthenticationProvider import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.beans.factory.annotation.Autowired @@ -16,6 +17,7 @@ import org.springframework.test.context.TestPropertySource import spock.lang.Specification import static javasabr.mqtt.auth.api.AuthenticationMethod.ANONYMOUS +import static javasabr.mqtt.auth.api.AuthenticationMethod.BASIC class AuthenticationProviderTest extends IntegrationSpecification { @@ -36,8 +38,8 @@ class AuthenticationProviderTest extends IntegrationSpecification { (credentialsSource instanceof FileCredentialsSource) and: verifyEach(authenticationProviders) { provider -> - provider.authenticationMethod != ANONYMOUS - !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationMethod == BASIC + provider instanceof BasicAuthenticationProvider } } } @@ -56,7 +58,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } and: authenticationProviders.any { provider -> - provider.authenticationMethod != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) + provider.authenticationMethod == BASIC && provider instanceof BasicAuthenticationProvider } } } @@ -66,13 +68,9 @@ class AuthenticationProviderTest extends IntegrationSpecification { def "should create anonymous authentication provider"() { expect: - authenticationProviders.any { provider -> + authenticationProviders.every { provider -> provider.authenticationMethod == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider } - and: - !authenticationProviders.any { provider -> - provider.authenticationMethod != ANONYMOUS && !(provider instanceof AnonymousAuthenticationProvider) - } } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy similarity index 79% rename from authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy rename to authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index b1de1d3d..bf88b27c 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseAuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -1,11 +1,11 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.MqttCredentials +import javasabr.mqtt.auth.api.AuthenticationMethod import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService -import javasabr.mqtt.auth.api.AuthenticationMethod import javasabr.mqtt.auth.api.CredentialsSource -import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource +import javasabr.mqtt.auth.api.CredentialsSourceType +import javasabr.mqtt.auth.api.MqttCredentials import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.DynamicPropertyRegistry @@ -20,13 +20,14 @@ import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ "authentication.allow-anonymous=false", "authentication.method.basic.enabled=true", + "authentication.credentials-source.file.enabled=true", "authentication.credentials-source.database.enabled=true" ]) @Testcontainers -class DatabaseAuthenticationServiceTest extends IntegrationSpecification { +class AuthenticationServiceTest extends IntegrationSpecification { @Autowired - CredentialsSource credentialsSource + List credentialsSources @Autowired List authenticationProviders @@ -47,7 +48,7 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { @Autowired AuthenticationService authenticationService - def "should authenticate credentials according [credentials/test] file"() { + def "should authenticate credentials according test database"() { given: def passwordBytes = password.getBytes(StandardCharsets.UTF_8) def request = new MqttCredentials(userName, passwordBytes, null, new byte[0]) @@ -61,11 +62,18 @@ class DatabaseAuthenticationServiceTest extends IntegrationSpecification { "user" | "correct-password" | true "" | "correct-password" | false "user" | "" | false + "user1" | "correct-password" | true } def "should create file credentials source and basic authentication provider"() { + given: + def expectedSourceTypes = [CredentialsSourceType.FILE, CredentialsSourceType.DATABASE] expect: - credentialsSource instanceof DatabaseCredentialsSource + verifyEach(credentialsSources) { credentialsSource -> + expectedSourceTypes.remove(credentialsSource.credentialsSourceType) + } + expectedSourceTypes.isEmpty() + and: verifyEach(authenticationProviders) { provider -> provider.authenticationMethod != AuthenticationMethod.ANONYMOUS !(provider instanceof AnonymousAuthenticationProvider) diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy deleted file mode 100644 index 552bf323..00000000 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/FileAuthenticationServiceTest.groovy +++ /dev/null @@ -1,34 +0,0 @@ -package javasabr.mqtt.broker.application.service - -import javasabr.mqtt.auth.api.MqttCredentials -import javasabr.mqtt.auth.api.AuthenticationService -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.TestPropertySource - -import java.nio.charset.StandardCharsets - -@TestPropertySource(properties = [ - "authentication.credentials-source.file.enabled=true", - "authentication.method.basic.enabled=true" -]) -class FileAuthenticationServiceTest extends IntegrationSpecification { - - @Autowired - AuthenticationService authenticationService - - def "should authenticate credentials according [credentials/test] file"() { - given: - def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new MqttCredentials(userName, passwordBytes, null, new byte[0]) - when: - def result = authenticationService.authenticate(request).block() - then: - result == expectedResult - where: - userName | password | expectedResult - "user" | "wrong-password" | false - "user" | "correct-password" | true - "" | "correct-password" | false - "user" | "" | false - } -} From de1412bee722b196a83d12cd17b0c44b429d6d5e Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:06:18 +0100 Subject: [PATCH 063/107] [broker-141] Extract FileCredentialsSourceSpringConfig --- .../provider/BasicAuthenticationProvider.java | 3 ++- .../AuthenticationServiceSpringConfig.java | 20 +++------------ .../FileCredentialsSourceSpringConfig.java | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 4a02cf34..1f52ed4e 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -41,6 +41,7 @@ public Mono authenticate(MqttCredentials credentials) { @Override public String toString() { return "{ \"authenticationMethod\": \"%s\", \"credentialSource\": %s }".formatted( - getAuthenticationMethod(), credentialsSources); + getAuthenticationMethod(), + credentialsSources); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index c5abb39d..c144504e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -5,13 +5,10 @@ import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; -import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -24,7 +21,10 @@ @CustomLog @Configuration(proxyBeanMethods = false) -@Import(DatabaseCredentialsSourceSpringConfig.class) +@Import({ + DatabaseCredentialsSourceSpringConfig.class, + FileCredentialsSourceSpringConfig.class +}) @EnableConfigurationProperties(AuthenticationProperties.class) public class AuthenticationServiceSpringConfig { @@ -38,18 +38,6 @@ AuthenticationService authenticationService( return new DefaultAuthenticationService(availableProviders, defaultAuthenticationMethod, anonymousProvider); } - @Bean - CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { - return authenticationProperties.credentialsSource().get(CredentialsSourceType.FILE); - } - - @Bean(initMethod = "init") - @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") - @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") - FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { - return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); - } - @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") @ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java new file mode 100644 index 00000000..c65b910e --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -0,0 +1,25 @@ +package javasabr.mqtt.auth.service.config; + +import javasabr.mqtt.auth.api.CredentialsSourceType; +import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") +@ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") +public class FileCredentialsSourceSpringConfig { + @Bean + CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { + return authenticationProperties.credentialsSource().get(CredentialsSourceType.FILE); + } + + @Bean(initMethod = "init") + FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { + return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); + } +} From aca3dd1ebb0da6f4f92a66e987e56622afbada6a Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:05:41 +0100 Subject: [PATCH 064/107] [broker-141] Rename property interfaces --- .../DatabaseCredentialsSourceSpringConfig.java | 12 ++++++------ .../config/property/CredentialsSourceProperties.java | 2 +- .../auth/service/config/property/DatabaseConfig.java | 4 ---- ...Config.java => DatabaseConnectionProperties.java} | 2 +- ...sePoolConfig.java => DatabasePoolProperties.java} | 2 +- .../service/config/property/DatabaseProperties.java | 5 +++++ ...tsConfig.java => DatabaseTimeoutsProperties.java} | 2 +- .../{FileConfig.java => FileProperties.java} | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{DatabaseConnectionConfig.java => DatabaseConnectionProperties.java} (74%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{DatabasePoolConfig.java => DatabasePoolProperties.java} (79%) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{DatabaseTimeoutsConfig.java => DatabaseTimeoutsProperties.java} (70%) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{FileConfig.java => FileProperties.java} (73%) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 015573c9..588d5303 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -19,10 +19,10 @@ import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseConfig; -import javasabr.mqtt.auth.service.config.property.DatabaseConnectionConfig; +import javasabr.mqtt.auth.service.config.property.DatabaseProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; -import javasabr.mqtt.auth.service.config.property.DatabasePoolConfig; +import javasabr.mqtt.auth.service.config.property.DatabasePoolProperties; import org.flywaydb.core.Flyway; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -48,7 +48,7 @@ public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationP @Bean ConnectionFactoryOptions connectionFactoryOptions( - DatabaseConfig dbCredentialsSourceProperties, + DatabaseProperties dbCredentialsSourceProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( "lock_timeout", dbCredentialsSourceProperties.lockTimeout(), @@ -72,7 +72,7 @@ DatabaseClient databaseClient(ConnectionFactory connectionFactory) { @Bean @DependsOn("flyway") ConnectionFactory connectionFactory( - DatabasePoolConfig dbCredentialsSourceProperties, + DatabasePoolProperties dbCredentialsSourceProperties, ConnectionFactoryOptions connectionFactoryOptions) { ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) @@ -84,7 +84,7 @@ ConnectionFactory connectionFactory( } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseConnectionConfig dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { + Flyway flyway(DatabaseConnectionProperties dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( dbCredentialsSourceProperties.dbDriver().value(), dbCredentialsSourceProperties.dbHost(), diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java index d0b5415a..352954ff 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java @@ -14,4 +14,4 @@ public record CredentialsSourceProperties( int initialPoolSize, int maxPoolSize, String lockTimeout, - String statementTimeout) implements FileConfig, DatabaseConfig {} + String statementTimeout) implements FileProperties, DatabaseProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java deleted file mode 100644 index d2b858ef..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConfig.java +++ /dev/null @@ -1,4 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public interface DatabaseConfig extends DatabasePoolConfig, DatabaseConnectionConfig, DatabaseTimeoutsConfig {} - diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java similarity index 74% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java index 38778c4a..86283427 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service.config.property; -public interface DatabaseConnectionConfig { +public interface DatabaseConnectionProperties { DatabaseDriver dbDriver(); String dbHost(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java similarity index 79% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java index eaee3a75..a0898b07 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java @@ -2,7 +2,7 @@ import java.time.Duration; -public interface DatabasePoolConfig { +public interface DatabasePoolProperties { Duration maxIdleTime(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java new file mode 100644 index 00000000..97838f6c --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.auth.service.config.property; + +public interface DatabaseProperties extends DatabasePoolProperties, DatabaseConnectionProperties, + DatabaseTimeoutsProperties {} + diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java similarity index 70% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java index 1e1b0012..1ff5839b 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service.config.property; -public interface DatabaseTimeoutsConfig { +public interface DatabaseTimeoutsProperties { String lockTimeout(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java similarity index 73% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java index 87346f88..5fbe5cb1 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java @@ -2,7 +2,7 @@ import java.net.URI; -public interface FileConfig { +public interface FileProperties { URI uriPath(); } From 0fe97ff686635e9c78df9c24835e2f978b7716dd Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:02:58 +0100 Subject: [PATCH 065/107] [broker-141] Improve DefaultAuthenticationService instantiation --- .../service/DefaultAuthenticationService.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 5403cae8..c0a1b6e3 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,12 +1,10 @@ package javasabr.mqtt.auth.service; -import static java.util.stream.Collectors.toMap; - import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; @@ -33,13 +31,19 @@ public DefaultAuthenticationService( if (configuredProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - this.availableProviders = new EnumMap<>(configuredProviders.stream() - .collect(toMap(AuthenticationProvider::getAuthenticationMethod, Function.identity()))); - this.defaultProvider = Optional.ofNullable(defaultMethod) - .map(this.availableProviders::get) - .orElseGet(configuredProviders::getFirst); - if (defaultProvider == null) { - throw new AuthenticationConfigException("[%s] method not found".formatted(defaultMethod)); + this.availableProviders = configuredProviders.stream() + .collect(Collectors.toMap( + AuthenticationProvider::getAuthenticationMethod, + Function.identity(), + (a, _) -> { + throw new AuthenticationConfigException("There are several [%s] authentication providers" + .formatted(a.getAuthenticationMethod())); + }, + () -> new EnumMap<>(AuthenticationMethod.class))); + this.defaultProvider = availableProviders.get(defaultMethod == null ? AuthenticationMethod.BASIC : defaultMethod); + if (defaultProvider == null && anonymousProvider == null) { + throw new AuthenticationConfigException("None of [%s, BASIC, ANONYMOUS] authentication provider configured" + .formatted(defaultMethod)); } this.anonymousProvider = anonymousProvider; log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); From 7518aaa9124f7291725339663940a27ea29012d3 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:38:07 +0100 Subject: [PATCH 066/107] [broker-141] Improve AuthenticationProviderTest --- .../application/service/AuthenticationProviderTest.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index d14bd89b..634599c1 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -68,8 +68,9 @@ class AuthenticationProviderTest extends IntegrationSpecification { def "should create anonymous authentication provider"() { expect: - authenticationProviders.every { provider -> - provider.authenticationMethod == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider + verifyEach(authenticationProviders) { provider -> + provider.authenticationMethod == ANONYMOUS + provider instanceof AnonymousAuthenticationProvider } } } From 7d96f201accf68add5edb656f9443a9647a1d7d5 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:01:33 +0100 Subject: [PATCH 067/107] [broker-141] Require empty password by anonymous provider --- .../mqtt/auth/service/AnonymousAuthenticationProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java index 7d544337..ae527143 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java @@ -3,6 +3,7 @@ import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.MqttCredentials; +import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.StringUtils; import reactor.core.publisher.Mono; @@ -14,7 +15,7 @@ public AuthenticationMethod getAuthenticationMethod() { @Override public Mono authenticate(MqttCredentials credentials) { - return Mono.just(StringUtils.isEmpty(credentials.username())); + return Mono.just(StringUtils.isEmpty(credentials.username()) && ArrayUtils.isEmpty(credentials.password())); } @Override From 44b8a4c4fd8e2f48d197b38664accc8808ab44c2 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:02:20 +0100 Subject: [PATCH 068/107] [broker-141] Apply formatting --- .../main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index 4cdc0800..28b4ea3e 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -28,7 +28,7 @@ public enum AuthenticationMethod { String value; - public static AuthenticationMethod fromValue(String value){ + public static AuthenticationMethod fromValue(String value) { return CACHE.get(value); } } From 6914d2eb222f3cb74c508266ccc08e510f42237a Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:04:57 +0100 Subject: [PATCH 069/107] [broker-141] Rename CredentialsSource.getCredentialsSourceType() --- .../main/java/javasabr/mqtt/auth/api/CredentialsSource.java | 2 +- .../mqtt/auth/provider/BasicAuthenticationProvider.java | 2 +- .../application/service/AuthenticationServiceTest.groovy | 2 +- .../auth/credentials/source/DatabaseCredentialsSource.java | 5 ++--- .../mqtt/auth/credentials/source/FileCredentialsSource.java | 5 ++--- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java index 64e23b0c..5b01986a 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -4,7 +4,7 @@ public interface CredentialsSource { - CredentialsSourceType getCredentialsSourceType(); + CredentialsSourceType getType(); Mono isCredentialsExists(MqttCredentials credentials); } diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 1f52ed4e..95366c8d 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -22,7 +22,7 @@ public class BasicAuthenticationProvider implements AuthenticationProvider { public BasicAuthenticationProvider(List credentialsSources) { this.credentialsSources = credentialsSources.stream() - .sorted(comparingInt(credentialsSource -> credentialsSource.getCredentialsSourceType().priority())) + .sorted(comparingInt(credentialsSource -> credentialsSource.getType().priority())) .collect(ArrayCollectors.toArray(CredentialsSource.class)); } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index bf88b27c..f839cf66 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -70,7 +70,7 @@ class AuthenticationServiceTest extends IntegrationSpecification { def expectedSourceTypes = [CredentialsSourceType.FILE, CredentialsSourceType.DATABASE] expect: verifyEach(credentialsSources) { credentialsSource -> - expectedSourceTypes.remove(credentialsSource.credentialsSourceType) + expectedSourceTypes.remove(credentialsSource.type) } expectedSourceTypes.isEmpty() and: diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index c5428d7e..94497e9c 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,6 +1,5 @@ package javasabr.mqtt.auth.credentials.source; -import com.fasterxml.jackson.annotation.JsonValue; import java.util.Arrays; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; @@ -25,7 +24,7 @@ public class DatabaseCredentialsSource implements CredentialsSource { DatabaseClient databaseClient; @Override - public CredentialsSourceType getCredentialsSourceType() { + public CredentialsSourceType getType() { return CredentialsSourceType.DATABASE; } @@ -44,6 +43,6 @@ public Mono isCredentialsExists(MqttCredentials credentials) { @Override public String toString() { String dbDriver = databaseClient.getConnectionFactory().getMetadata().getName(); - return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getCredentialsSourceType(), dbDriver); + return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getType(), dbDriver); } } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 50f8c90f..438fd58b 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -1,6 +1,5 @@ package javasabr.mqtt.auth.credentials.source; -import com.fasterxml.jackson.annotation.JsonValue; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -31,14 +30,14 @@ public void init() { } @Override - public CredentialsSourceType getCredentialsSourceType() { + public CredentialsSourceType getType() { return CredentialsSourceType.FILE; } @Override public String toString() { return "{ \"credentialsSource\": \"%s\", \"filePath\": \"%s\" }".formatted( - getCredentialsSourceType(), + getType(), fileName.getPath()); } } From bbcf4ad9fb2cd993ce8287a88fb481f08978838f Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:24:05 +0100 Subject: [PATCH 070/107] [broker-141] Separate FileProperties and DatabaseProperties --- .../AuthenticationServiceSpringConfig.java | 8 +++++++- .../FileCredentialsSourceSpringConfig.java | 10 ++-------- .../property/AuthenticationProperties.java | 8 +------- .../property/CredentialsSourceProperties.java | 17 ----------------- .../DatabaseCredentialsSourceProperties.java | 19 +++++++++++++++++++ .../FileCredentialsSourceProperties.java | 9 +++++++++ 6 files changed, 38 insertions(+), 33 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index c144504e..d96b4c90 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -9,6 +9,8 @@ import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; +import javasabr.mqtt.auth.service.config.property.DatabaseCredentialsSourceProperties; +import javasabr.mqtt.auth.service.config.property.FileCredentialsSourceProperties; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -25,7 +27,11 @@ DatabaseCredentialsSourceSpringConfig.class, FileCredentialsSourceSpringConfig.class }) -@EnableConfigurationProperties(AuthenticationProperties.class) +@EnableConfigurationProperties({ + AuthenticationProperties.class, + FileCredentialsSourceProperties.class, + DatabaseCredentialsSourceProperties.class +}) public class AuthenticationServiceSpringConfig { @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index c65b910e..91bb2e2f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -1,9 +1,7 @@ package javasabr.mqtt.auth.service.config; -import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; +import javasabr.mqtt.auth.api.FileProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -13,13 +11,9 @@ @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") public class FileCredentialsSourceSpringConfig { - @Bean - CredentialsSourceProperties fileCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { - return authenticationProperties.credentialsSource().get(CredentialsSourceType.FILE); - } @Bean(initMethod = "init") - FileCredentialsSource fileCredentialsSource(CredentialsSourceProperties fileCredentialsSourceProperties) { + FileCredentialsSource fileCredentialsSource(FileProperties fileCredentialsSourceProperties) { return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index 01bd6929..db20ee26 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -1,13 +1,7 @@ package javasabr.mqtt.auth.service.config.property; -import java.util.Map; import javasabr.mqtt.auth.api.AuthenticationMethod; -import javasabr.mqtt.auth.api.CredentialsSourceType; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties( - boolean allowAnonymous, - AuthenticationMethod defaultMethod, - Map method, - Map credentialsSource) {} +public record AuthenticationProperties(boolean allowAnonymous, AuthenticationMethod defaultMethod) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java deleted file mode 100644 index 352954ff..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/CredentialsSourceProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.net.URI; -import java.time.Duration; - -public record CredentialsSourceProperties( - boolean enabled, - URI uriPath, - DatabaseDriver dbDriver, - String dbHost, - int dbPort, - String dbName, - Duration maxIdleTime, - int initialPoolSize, - int maxPoolSize, - String lockTimeout, - String statementTimeout) implements FileProperties, DatabaseProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java new file mode 100644 index 00000000..75bb524a --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java @@ -0,0 +1,19 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.time.Duration; +import javasabr.mqtt.auth.api.DatabaseDriver; +import javasabr.mqtt.auth.api.DatabaseProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("authentication.credentials-source.database") +public record DatabaseCredentialsSourceProperties( + boolean enabled, + DatabaseDriver dbDriver, + String dbHost, + int dbPort, + String dbName, + Duration maxIdleTime, + int initialPoolSize, + int maxPoolSize, + String lockTimeout, + String statementTimeout) implements DatabaseProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java new file mode 100644 index 00000000..8dee6017 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java @@ -0,0 +1,9 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.net.URI; +import javasabr.mqtt.auth.api.FileProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("authentication.credentials-source.file") +public record FileCredentialsSourceProperties( + boolean enabled, URI uriPath) implements FileProperties {} From 9bbc1828a1e8cadee967ff2a4978702c0192f6ee Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:11:07 +0100 Subject: [PATCH 071/107] [broker-141] Remove db related dependencies from authentication-service --- .../api}/DatabaseConnectionProperties.java | 2 +- .../mqtt/auth/api}/DatabaseCredentials.java | 2 +- .../mqtt/auth/api}/DatabaseDriver.java | 2 +- .../auth/api}/DatabasePoolProperties.java | 2 +- .../mqtt/auth/api}/DatabaseProperties.java | 2 +- .../auth/api}/DatabaseTimeoutsProperties.java | 2 +- .../mqtt/auth/api}/FileProperties.java | 2 +- authentication-service/build.gradle | 10 +- ...DatabaseCredentialsSourceSpringConfig.java | 91 ++++--------------- .../service/DatabaseTestSpringConfig.java | 2 +- credentials-source-db/build.gradle | 7 +- .../source/DatabaseCredentialsSource.java | 40 +++++--- .../DatabaseCredentialsSourceFactories.java | 64 +++++++++++++ 13 files changed, 123 insertions(+), 105 deletions(-) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabaseConnectionProperties.java (72%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabaseCredentials.java (58%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabaseDriver.java (87%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabasePoolProperties.java (74%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabaseProperties.java (72%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/DatabaseTimeoutsProperties.java (66%) rename {authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property => authentication-api/src/main/java/javasabr/mqtt/auth/api}/FileProperties.java (59%) create mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java similarity index 72% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java index 86283427..001c5714 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; public interface DatabaseConnectionProperties { DatabaseDriver dbDriver(); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java similarity index 58% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java index 50d66c82..910a46e1 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; public record DatabaseCredentials(String username, String password) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java similarity index 87% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java index 59898b31..68420bb5 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseDriver.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; import lombok.AccessLevel; import lombok.Getter; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java similarity index 74% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java index a0898b07..31d235c6 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; import java.time.Duration; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java similarity index 72% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java index 97838f6c..a4a92d17 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; public interface DatabaseProperties extends DatabasePoolProperties, DatabaseConnectionProperties, DatabaseTimeoutsProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java similarity index 66% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java index 1ff5839b..776d78ac 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseTimeoutsProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; public interface DatabaseTimeoutsProperties { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java similarity index 59% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java index 5fbe5cb1..2d8c0915 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.service.config.property; +package javasabr.mqtt.auth.api; import java.net.URI; diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index 4dbad706..cade8c59 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -16,15 +16,7 @@ dependencies { implementation libs.springboot.starter.autoconfigure implementation libs.springboot.starter.log4j2 implementation libs.rlib.logger.slf4j - // database support - implementation libs.r2dbc.spi - implementation libs.r2dbc.pool - implementation libs.r2dbc.postgresql - implementation libs.jdbc.postgres - implementation libs.flyway.core - implementation libs.flyway.postgresql - implementation libs.spring.r2dbc - // test support + testImplementation projects.credentialsSourceDb testImplementation projects.credentialsSourceFile testImplementation projects.authenticationProviderBasic diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 588d5303..be754d51 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -1,97 +1,40 @@ package javasabr.mqtt.auth.service.config; -import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; -import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; -import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSource; -import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.CredentialsSourceProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseConnectionProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; -import javasabr.mqtt.auth.service.config.property.DatabasePoolProperties; +import javasabr.mqtt.auth.api.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.DatabaseCredentials; +import javasabr.mqtt.auth.api.DatabaseProperties; +import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; import org.flywaydb.core.Flyway; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; -import org.springframework.r2dbc.core.DatabaseClient; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") public class DatabaseCredentialsSourceSpringConfig { - @Bean - CredentialsSource dbCredentialsSource(DatabaseClient databaseClient) { - return new DatabaseCredentialsSource(databaseClient); - } - - @Bean - public CredentialsSourceProperties dbCredentialsSourceProperties(AuthenticationProperties authenticationProperties) { - return authenticationProperties.credentialsSource().get(CredentialsSourceType.DATABASE); - } - - @Bean - ConnectionFactoryOptions connectionFactoryOptions( - DatabaseProperties dbCredentialsSourceProperties, - DatabaseCredentials readerDatabaseCredentials) { - Map timeoutOptions = Map.of( - "lock_timeout", dbCredentialsSourceProperties.lockTimeout(), - "statement_timeout", dbCredentialsSourceProperties.statementTimeout()); - return ConnectionFactoryOptions.builder() - .option(DATABASE, dbCredentialsSourceProperties.dbName()) - .option(DRIVER, dbCredentialsSourceProperties.dbDriver().value()) - .option(HOST, dbCredentialsSourceProperties.dbHost()) - .option(PORT, dbCredentialsSourceProperties.dbPort()) - .option(USER, readerDatabaseCredentials.username()) - .option(PASSWORD, readerDatabaseCredentials.password()) - .option(OPTIONS, timeoutOptions) - .build(); - } - - @Bean - DatabaseClient databaseClient(ConnectionFactory connectionFactory) { - return DatabaseClient.create(connectionFactory); - } - @Bean @DependsOn("flyway") - ConnectionFactory connectionFactory( - DatabasePoolProperties dbCredentialsSourceProperties, - ConnectionFactoryOptions connectionFactoryOptions) { - ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(dbCredentialsSourceProperties.maxIdleTime()) - .maxSize(dbCredentialsSourceProperties.maxPoolSize()) - .initialSize(dbCredentialsSourceProperties.initialPoolSize()) + CredentialsSource dbCredentialsSource( + DatabaseProperties databaseCredentialsSourceProperties, + DatabaseCredentials readerDatabaseCredentials) { + return DatabaseCredentialsSourceFactories.databaseCredentialsSource() + .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) + .readerDatabaseCredentials(readerDatabaseCredentials) .build(); - return new ConnectionPool(configuration); } @Bean(initMethod = "migrate") - Flyway flyway(DatabaseConnectionProperties dbCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { - String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - dbCredentialsSourceProperties.dbDriver().value(), - dbCredentialsSourceProperties.dbHost(), - dbCredentialsSourceProperties.dbPort(), - dbCredentialsSourceProperties.dbName()); - return Flyway.configure() - .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) - .load(); + Flyway flyway( + DatabaseConnectionProperties databaseCredentialsSourceProperties, + DatabaseCredentials adminDatabaseCredentials) { + return DatabaseCredentialsSourceFactories.flyway() + .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) + .adminDatabaseCredentials(adminDatabaseCredentials) + .build(); } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index f3fc690a..2a6fe014 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service; -import javasabr.mqtt.auth.service.config.property.DatabaseCredentials; +import javasabr.mqtt.auth.api.DatabaseCredentials; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/credentials-source-db/build.gradle b/credentials-source-db/build.gradle index 73b35cfd..f0927faf 100644 --- a/credentials-source-db/build.gradle +++ b/credentials-source-db/build.gradle @@ -9,7 +9,12 @@ description = "Database-based Credentials Source Provider" dependencies { api projects.base api projects.authenticationApi - api libs.spring.r2dbc + implementation libs.r2dbc.spi + implementation libs.r2dbc.pool + implementation libs.r2dbc.postgresql + implementation libs.jdbc.postgres + api libs.flyway.core + implementation libs.flyway.postgresql testImplementation projects.testSupport testFixturesApi projects.testSupport diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 94497e9c..0c4909c6 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,13 +1,16 @@ package javasabr.mqtt.auth.credentials.source; -import java.util.Arrays; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.Result; +import java.util.Objects; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.MqttCredentials; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.r2dbc.core.DatabaseClient; +import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @RequiredArgsConstructor @@ -16,12 +19,17 @@ public class DatabaseCredentialsSource implements CredentialsSource { @SuppressWarnings("SqlNoDataSourceInspection") private static final String CREDENTIALS_QUERY = """ - SELECT password + SELECT COUNT(*) > 0 FROM user_credentials WHERE username = $1 + AND password = $2; """; - DatabaseClient databaseClient; + private static Mono isCredentialsRecordFound(Result result) { + return Mono.from(result.map((row, _) -> Objects.equals(row.get(0, Boolean.class), Boolean.TRUE))); + } + + ConnectionFactory connectionFactory; @Override public CredentialsSourceType getType() { @@ -30,19 +38,25 @@ public CredentialsSourceType getType() { @Override public Mono isCredentialsExists(MqttCredentials credentials) { - return databaseClient - .sql(CREDENTIALS_QUERY) - .bind("$1", credentials.username()) - .map(row -> row.get("password", byte[].class)) - .all() - .singleOrEmpty() - .map(existingPassword -> Arrays.equals(existingPassword, credentials.password())) - .defaultIfEmpty(false); + return Mono.usingWhen( + connectionFactory.create(), + connection -> executeCredentialsQuery(connection, credentials), + Connection::close); } @Override public String toString() { - String dbDriver = databaseClient.getConnectionFactory().getMetadata().getName(); + String dbDriver = connectionFactory.getMetadata().getName(); return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getType(), dbDriver); } + + private Mono executeCredentialsQuery(Connection connection, MqttCredentials credentials) { + Publisher credentialsQuery = connection.createStatement(CREDENTIALS_QUERY) + .bind("$1", credentials.username()) + .bind("$2", credentials.password()) + .execute(); + return Mono.from(credentialsQuery) + .flatMap(DatabaseCredentialsSource::isCredentialsRecordFound) + .defaultIfEmpty(false); + } } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java new file mode 100644 index 00000000..0a93cf17 --- /dev/null +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java @@ -0,0 +1,64 @@ +package javasabr.mqtt.auth.credentials.source.config; + +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import java.util.Map; +import javasabr.mqtt.auth.api.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.DatabaseCredentials; +import javasabr.mqtt.auth.api.DatabaseProperties; +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; +import lombok.Builder; +import org.flywaydb.core.Flyway; + +public class DatabaseCredentialsSourceFactories { + + @Builder(builderMethodName = "flyway") + private static Flyway createFlyway( + DatabaseConnectionProperties databaseCredentialsSourceProperties, + DatabaseCredentials adminDatabaseCredentials) { + String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( + databaseCredentialsSourceProperties.dbDriver().value(), + databaseCredentialsSourceProperties.dbHost(), + databaseCredentialsSourceProperties.dbPort(), + databaseCredentialsSourceProperties.dbName()); + return Flyway.configure() + .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) + .load(); + } + + @Builder(builderMethodName = "databaseCredentialsSource") + private static DatabaseCredentialsSource createDatabaseCredentialsSource( + DatabaseProperties databaseCredentialsSourceProperties, + DatabaseCredentials readerDatabaseCredentials) { + Map timeoutOptions = Map.of( + "lock_timeout", databaseCredentialsSourceProperties.lockTimeout(), + "statement_timeout", databaseCredentialsSourceProperties.statementTimeout()); + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() + .option(DATABASE, databaseCredentialsSourceProperties.dbName()) + .option(DRIVER, databaseCredentialsSourceProperties.dbDriver().value()) + .option(HOST, databaseCredentialsSourceProperties.dbHost()) + .option(PORT, databaseCredentialsSourceProperties.dbPort()) + .option(USER, readerDatabaseCredentials.username()) + .option(PASSWORD, readerDatabaseCredentials.password()) + .option(OPTIONS, timeoutOptions).build(); + ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) + .maxIdleTime(databaseCredentialsSourceProperties.maxIdleTime()) + .maxSize(databaseCredentialsSourceProperties.maxPoolSize()) + .initialSize(databaseCredentialsSourceProperties.initialPoolSize()) + .build(); + ConnectionPool connectionPool = new ConnectionPool(configuration); + return new DatabaseCredentialsSource(connectionPool); + } +} From 8c73b0ae4fdd15e49eeb62a1b708c8d24b43ac4e Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:12:11 +0100 Subject: [PATCH 072/107] [broker-141] Remove not implemented credentials source types --- .../java/javasabr/mqtt/auth/api/CredentialsSourceType.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java index 1aff7cf7..56a25434 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSourceType.java @@ -12,11 +12,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum CredentialsSourceType { FILE(1), - REDIS(2), - DATABASE(3), - LDAP(4), - HTTP(5), - SYSTEM(6); + DATABASE(2); int priority; } From ce8684e2d9c98f63d70bcc4b5ff11fc754ac1b24 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:12:33 +0100 Subject: [PATCH 073/107] [broker-141] Extract onDuplicateProviderErrorHandler --- .../auth/service/DefaultAuthenticationService.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index c0a1b6e3..89fb6ad4 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -35,10 +35,7 @@ public DefaultAuthenticationService( .collect(Collectors.toMap( AuthenticationProvider::getAuthenticationMethod, Function.identity(), - (a, _) -> { - throw new AuthenticationConfigException("There are several [%s] authentication providers" - .formatted(a.getAuthenticationMethod())); - }, + DefaultAuthenticationService::onDuplicateProviderErrorHandler, () -> new EnumMap<>(AuthenticationMethod.class))); this.defaultProvider = availableProviders.get(defaultMethod == null ? AuthenticationMethod.BASIC : defaultMethod); if (defaultProvider == null && anonymousProvider == null) { @@ -49,6 +46,13 @@ public DefaultAuthenticationService( log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); } + private static AuthenticationProvider onDuplicateProviderErrorHandler( + AuthenticationProvider first, + AuthenticationProvider second) { + throw new AuthenticationConfigException("There are several [%s] authentication providers" + .formatted(first.getAuthenticationMethod())); + } + @Override public Mono authenticate(MqttCredentials request) { AuthenticationMethod authenticationMethod = request.authenticationMethod(); From e9b7f277f81eff69a230db2396047490303c809d Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:31:40 +0100 Subject: [PATCH 074/107] [broker-141] Refactoring of authentication properties structure --- .../resources/application-test.properties | 3 +- .../mqtt/auth/api/CredentialsSource.java | 2 +- .../api/DatabaseConnectionProperties.java | 8 ++--- .../mqtt/auth/api/DatabasePoolProperties.java | 4 +-- .../mqtt/auth/api/DatabaseProperties.java | 5 --- .../mqtt/auth/api/FileProperties.java | 2 +- .../auth/api/InMemoryCredentialsSource.java | 2 +- .../provider/BasicAuthenticationProvider.java | 2 +- ...DatabaseCredentialsSourceSpringConfig.java | 22 ++++++++++--- .../FileCredentialsSourceSpringConfig.java | 7 ++-- .../AuthenticationProviderProperties.java | 3 -- ...BasicAuthenticationProviderProperties.java | 3 -- .../DatabaseCredentialsSourceProperties.java | 19 ----------- .../SpringDatabaseConnectionProperties.java | 10 ++++++ .../SpringDatabasePoolProperties.java | 10 ++++++ .../SpringDatabaseTimeoutsProperties.java | 9 +++++ ...perties.java => SpringFileProperties.java} | 5 ++- .../service/AuthenticationServiceTest.groovy | 2 +- .../resources/application-test.properties | 20 +++++------ .../source/DatabaseCredentialsSource.java | 2 +- .../DatabaseCredentialsSourceFactories.java | 33 ++++++++++--------- 21 files changed, 95 insertions(+), 78 deletions(-) delete mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{FileCredentialsSourceProperties.java => SpringFileProperties.java} (52%) diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index dcdf128c..f40a842c 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,9 +1,8 @@ authentication.allow-anonymous=true authentication.default-method=basic authentication.method.basic.enabled=true -authentication.method.basic.credentials-sources=file authentication.credentials-source.file.enabled=true -authentication.credentials-source.file.uri-path=classpath:auth/credentials-test +authentication.credentials-source.file.path=classpath:auth/credentials-test mqtt.external.connection.shared.subscription.available=true mqtt.external.connection.wildcard.subscription.available=true diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java index 5b01986a..9e46d36c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/CredentialsSource.java @@ -6,5 +6,5 @@ public interface CredentialsSource { CredentialsSourceType getType(); - Mono isCredentialsExists(MqttCredentials credentials); + Mono isCredentialsValid(MqttCredentials credentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java index 001c5714..1d56ffca 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java @@ -1,12 +1,12 @@ package javasabr.mqtt.auth.api; public interface DatabaseConnectionProperties { - DatabaseDriver dbDriver(); + DatabaseDriver driver(); - String dbHost(); + String host(); - int dbPort(); + int port(); - String dbName(); + String name(); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java index 31d235c6..e821d210 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java @@ -6,8 +6,8 @@ public interface DatabasePoolProperties { Duration maxIdleTime(); - int initialPoolSize(); + int initialSize(); - int maxPoolSize(); + int maxSize(); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java deleted file mode 100644 index a4a92d17..00000000 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseProperties.java +++ /dev/null @@ -1,5 +0,0 @@ -package javasabr.mqtt.auth.api; - -public interface DatabaseProperties extends DatabasePoolProperties, DatabaseConnectionProperties, - DatabaseTimeoutsProperties {} - diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java index 2d8c0915..912cad2e 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java @@ -4,5 +4,5 @@ public interface FileProperties { - URI uriPath(); + URI path(); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java index 12cbbf28..ad5ba146 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java @@ -47,7 +47,7 @@ protected void reset(InputStream inStream) throws IOException { } @Override - public Mono isCredentialsExists(MqttCredentials mqttCredentials) { + public Mono isCredentialsValid(MqttCredentials mqttCredentials) { return Mono.just(Arrays.equals(mqttCredentials.password(), credentials.get(mqttCredentials.username()))); } } diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index 95366c8d..b45dca3a 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -34,7 +34,7 @@ public AuthenticationMethod getAuthenticationMethod() { @Override public Mono authenticate(MqttCredentials credentials) { return Flux.fromIterable(credentialsSources) - .concatMap(credentialsSource -> credentialsSource.isCredentialsExists(credentials)) + .concatMap(credentialsSource -> credentialsSource.isCredentialsValid(credentials)) .any(Boolean::booleanValue); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index be754d51..14ee5747 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -3,11 +3,16 @@ import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.DatabaseCredentials; -import javasabr.mqtt.auth.api.DatabaseProperties; +import javasabr.mqtt.auth.api.DatabasePoolProperties; +import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; +import javasabr.mqtt.auth.service.config.property.SpringDatabaseConnectionProperties; +import javasabr.mqtt.auth.service.config.property.SpringDatabasePoolProperties; +import javasabr.mqtt.auth.service.config.property.SpringDatabaseTimeoutsProperties; import org.flywaydb.core.Flyway; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @@ -15,22 +20,31 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") +@EnableConfigurationProperties({ + SpringDatabasePoolProperties.class, + SpringDatabaseTimeoutsProperties.class, + SpringDatabaseConnectionProperties.class +}) public class DatabaseCredentialsSourceSpringConfig { @Bean @DependsOn("flyway") CredentialsSource dbCredentialsSource( - DatabaseProperties databaseCredentialsSourceProperties, + DatabasePoolProperties databasePoolProperties, + DatabaseTimeoutsProperties databaseTimeoutsProperties, + DatabaseConnectionProperties databaseConnectionProperties , DatabaseCredentials readerDatabaseCredentials) { return DatabaseCredentialsSourceFactories.databaseCredentialsSource() - .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) + .databasePoolProperties(databasePoolProperties) + .databaseTimeoutsProperties(databaseTimeoutsProperties) + .databaseConnectionProperties(databaseConnectionProperties) .readerDatabaseCredentials(readerDatabaseCredentials) .build(); } @Bean(initMethod = "migrate") Flyway flyway( - DatabaseConnectionProperties databaseCredentialsSourceProperties, + SpringDatabaseConnectionProperties databaseCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { return DatabaseCredentialsSourceFactories.flyway() .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index 91bb2e2f..82033ba7 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -1,19 +1,22 @@ package javasabr.mqtt.auth.service.config; -import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import javasabr.mqtt.auth.api.FileProperties; +import javasabr.mqtt.auth.service.config.property.SpringFileProperties; +import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") +@EnableConfigurationProperties(SpringFileProperties.class) public class FileCredentialsSourceSpringConfig { @Bean(initMethod = "init") FileCredentialsSource fileCredentialsSource(FileProperties fileCredentialsSourceProperties) { - return new FileCredentialsSource(fileCredentialsSourceProperties.uriPath()); + return new FileCredentialsSource(fileCredentialsSourceProperties.path()); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java deleted file mode 100644 index ac08e918..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProviderProperties.java +++ /dev/null @@ -1,3 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public record AuthenticationProviderProperties(boolean enabled) implements BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java deleted file mode 100644 index 74b1e8fa..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/BasicAuthenticationProviderProperties.java +++ /dev/null @@ -1,3 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -public interface BasicAuthenticationProviderProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java deleted file mode 100644 index 75bb524a..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DatabaseCredentialsSourceProperties.java +++ /dev/null @@ -1,19 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.time.Duration; -import javasabr.mqtt.auth.api.DatabaseDriver; -import javasabr.mqtt.auth.api.DatabaseProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("authentication.credentials-source.database") -public record DatabaseCredentialsSourceProperties( - boolean enabled, - DatabaseDriver dbDriver, - String dbHost, - int dbPort, - String dbName, - Duration maxIdleTime, - int initialPoolSize, - int maxPoolSize, - String lockTimeout, - String statementTimeout) implements DatabaseProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java new file mode 100644 index 00000000..b4f4b1a2 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.auth.service.config.property; + +import javasabr.mqtt.auth.api.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.DatabaseDriver; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "authentication.credentials-source.database") +public record SpringDatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String name) implements + DatabaseConnectionProperties {} + diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java new file mode 100644 index 00000000..569af14e --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.auth.service.config.property; + +import java.time.Duration; +import javasabr.mqtt.auth.api.DatabasePoolProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "authentication.credentials-source.database.pool") +public record SpringDatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) implements + DatabasePoolProperties {} + diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java new file mode 100644 index 00000000..434f1ebb --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java @@ -0,0 +1,9 @@ +package javasabr.mqtt.auth.service.config.property; + +import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "authentication.credentials-source.database.timeout") +public record SpringDatabaseTimeoutsProperties(String lockTimeout, String statementTimeout) implements + DatabaseTimeoutsProperties {} + diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java similarity index 52% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java index 8dee6017..40b4f6c6 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/FileCredentialsSourceProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java @@ -4,6 +4,5 @@ import javasabr.mqtt.auth.api.FileProperties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("authentication.credentials-source.file") -public record FileCredentialsSourceProperties( - boolean enabled, URI uriPath) implements FileProperties {} +@ConfigurationProperties(prefix = "authentication.credentials-source.file") +public record SpringFileProperties(boolean enabled, URI path) implements FileProperties {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index f839cf66..2dd127b1 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -41,7 +41,7 @@ class AuthenticationServiceTest extends IntegrationSpecification { static void configureProperties(DynamicPropertyRegistry registry) { postgreSQLContainer.start() registry.add( - "authentication.credentials-source.database.db-port", + "authentication.credentials-source.database.port", { "${postgreSQLContainer.getMappedPort(5432)}" }) } diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index 34d228d6..b50effaf 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,11 +1,11 @@ authentication.method.basic.enabled=true -authentication.credentials-source.file.uri-path=classpath:credentials/test -authentication.credentials-source.database.db-driver=postgresql -authentication.credentials-source.database.db-host=localhost -authentication.credentials-source.database.db-port=5432 -authentication.credentials-source.database.db-name=testdb -authentication.credentials-source.database.max-idle-time=30m -authentication.credentials-source.database.initial-pool-size=5 -authentication.credentials-source.database.max-pool-size=10 -authentication.credentials-source.database.lock-timeout=10s -authentication.credentials-source.database.statement-timeout=5min +authentication.credentials-source.file.path=classpath:credentials/test +authentication.credentials-source.database.driver=postgresql +authentication.credentials-source.database.host=localhost +authentication.credentials-source.database.port=5432 +authentication.credentials-source.database.name=testdb +authentication.credentials-source.database.pool.max-idle-time=30m +authentication.credentials-source.database.pool.initial-size=5 +authentication.credentials-source.database.pool.max-size=10 +authentication.credentials-source.database.timeout.lock-timeout=10s +authentication.credentials-source.database.timeout.statement-timeout=5min diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 0c4909c6..feea126c 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -37,7 +37,7 @@ public CredentialsSourceType getType() { } @Override - public Mono isCredentialsExists(MqttCredentials credentials) { + public Mono isCredentialsValid(MqttCredentials credentials) { return Mono.usingWhen( connectionFactory.create(), connection -> executeCredentialsQuery(connection, credentials), diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java index 0a93cf17..60e8b679 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java @@ -16,7 +16,8 @@ import java.util.Map; import javasabr.mqtt.auth.api.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.DatabaseCredentials; -import javasabr.mqtt.auth.api.DatabaseProperties; +import javasabr.mqtt.auth.api.DatabasePoolProperties; +import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import lombok.Builder; import org.flywaydb.core.Flyway; @@ -28,10 +29,10 @@ private static Flyway createFlyway( DatabaseConnectionProperties databaseCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - databaseCredentialsSourceProperties.dbDriver().value(), - databaseCredentialsSourceProperties.dbHost(), - databaseCredentialsSourceProperties.dbPort(), - databaseCredentialsSourceProperties.dbName()); + databaseCredentialsSourceProperties.driver().value(), + databaseCredentialsSourceProperties.host(), + databaseCredentialsSourceProperties.port(), + databaseCredentialsSourceProperties.name()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); @@ -39,24 +40,26 @@ private static Flyway createFlyway( @Builder(builderMethodName = "databaseCredentialsSource") private static DatabaseCredentialsSource createDatabaseCredentialsSource( - DatabaseProperties databaseCredentialsSourceProperties, + DatabasePoolProperties databasePoolProperties, + DatabaseTimeoutsProperties databaseTimeoutsProperties, + DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( - "lock_timeout", databaseCredentialsSourceProperties.lockTimeout(), - "statement_timeout", databaseCredentialsSourceProperties.statementTimeout()); + "lock_timeout", databaseTimeoutsProperties.lockTimeout(), + "statement_timeout", databaseTimeoutsProperties.statementTimeout()); ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() - .option(DATABASE, databaseCredentialsSourceProperties.dbName()) - .option(DRIVER, databaseCredentialsSourceProperties.dbDriver().value()) - .option(HOST, databaseCredentialsSourceProperties.dbHost()) - .option(PORT, databaseCredentialsSourceProperties.dbPort()) + .option(DATABASE, databaseConnectionProperties.name()) + .option(DRIVER, databaseConnectionProperties.driver().value()) + .option(HOST, databaseConnectionProperties.host()) + .option(PORT, databaseConnectionProperties.port()) .option(USER, readerDatabaseCredentials.username()) .option(PASSWORD, readerDatabaseCredentials.password()) .option(OPTIONS, timeoutOptions).build(); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(databaseCredentialsSourceProperties.maxIdleTime()) - .maxSize(databaseCredentialsSourceProperties.maxPoolSize()) - .initialSize(databaseCredentialsSourceProperties.initialPoolSize()) + .maxIdleTime(databasePoolProperties.maxIdleTime()) + .maxSize(databasePoolProperties.maxSize()) + .initialSize(databasePoolProperties.initialSize()) .build(); ConnectionPool connectionPool = new ConnectionPool(configuration); return new DatabaseCredentialsSource(connectionPool); From cfede3d8074d8c8c56f743cc96bbeea4f770055a Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:32:16 +0100 Subject: [PATCH 075/107] [broker-141] Do not keep AnonymousAuthenticationProvider separately --- .../auth/service/DefaultAuthenticationService.java | 10 ++++------ .../config/AuthenticationServiceSpringConfig.java | 12 +++--------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 89fb6ad4..e55e23f0 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -22,12 +22,11 @@ public class DefaultAuthenticationService implements AuthenticationService { Map availableProviders; AuthenticationProvider defaultProvider; - @Nullable AnonymousAuthenticationProvider anonymousProvider; public DefaultAuthenticationService( List configuredProviders, - @Nullable AuthenticationMethod defaultMethod, - @Nullable AnonymousAuthenticationProvider anonymousProvider) { + @Nullable AuthenticationMethod defaultMethod + ) { if (configuredProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } @@ -38,11 +37,10 @@ public DefaultAuthenticationService( DefaultAuthenticationService::onDuplicateProviderErrorHandler, () -> new EnumMap<>(AuthenticationMethod.class))); this.defaultProvider = availableProviders.get(defaultMethod == null ? AuthenticationMethod.BASIC : defaultMethod); - if (defaultProvider == null && anonymousProvider == null) { + if (defaultProvider == null && !availableProviders.containsKey(AuthenticationMethod.ANONYMOUS)) { throw new AuthenticationConfigException("None of [%s, BASIC, ANONYMOUS] authentication provider configured" .formatted(defaultMethod)); } - this.anonymousProvider = anonymousProvider; log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); } @@ -58,7 +56,7 @@ public Mono authenticate(MqttCredentials request) { AuthenticationMethod authenticationMethod = request.authenticationMethod(); AuthenticationProvider targetProvider = authenticationMethod == null ? defaultProvider : availableProviders.get(authenticationMethod); - return Mono.justOrEmpty(anonymousProvider) + return Mono.justOrEmpty(availableProviders.get(AuthenticationMethod.ANONYMOUS)) .flatMap(provider -> provider.authenticate(request)) .onErrorReturn(false) .filter(Boolean::booleanValue) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index d96b4c90..f00de1fa 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -9,10 +9,7 @@ import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; -import javasabr.mqtt.auth.service.config.property.DatabaseCredentialsSourceProperties; -import javasabr.mqtt.auth.service.config.property.FileCredentialsSourceProperties; import lombok.CustomLog; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -28,20 +25,17 @@ FileCredentialsSourceSpringConfig.class }) @EnableConfigurationProperties({ - AuthenticationProperties.class, - FileCredentialsSourceProperties.class, - DatabaseCredentialsSourceProperties.class + AuthenticationProperties.class }) public class AuthenticationServiceSpringConfig { @Bean AuthenticationService authenticationService( List availableProviders, - AuthenticationProperties authenticationProperties, - @Autowired(required = false) AnonymousAuthenticationProvider anonymousProvider) { + AuthenticationProperties authenticationProperties) { log.info("Initializing AuthenticationService..."); AuthenticationMethod defaultAuthenticationMethod = authenticationProperties.defaultMethod(); - return new DefaultAuthenticationService(availableProviders, defaultAuthenticationMethod, anonymousProvider); + return new DefaultAuthenticationService(availableProviders, defaultAuthenticationMethod); } @Bean From 97b2755434dd4c81d1c495c69e5319b5240ab2be Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:56:50 +0100 Subject: [PATCH 076/107] [broker-141] Move database properties classes --- .../{ => database}/DatabaseConnectionProperties.java | 2 +- .../auth/api/{ => database}/DatabaseCredentials.java | 2 +- .../mqtt/auth/api/{ => database}/DatabaseDriver.java | 2 +- .../api/{ => database}/DatabasePoolProperties.java | 2 +- .../{ => database}/DatabaseTimeoutsProperties.java | 2 +- .../DatabaseCredentialsSourceSpringConfig.java | 8 ++++---- .../property/SpringDatabaseConnectionProperties.java | 12 ++++++++---- .../property/SpringDatabasePoolProperties.java | 2 +- .../property/SpringDatabaseTimeoutsProperties.java | 2 +- .../service/DatabaseTestSpringConfig.java | 2 +- .../config/DatabaseCredentialsSourceFactories.java | 8 ++++---- 11 files changed, 24 insertions(+), 20 deletions(-) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => database}/DatabaseConnectionProperties.java (76%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => database}/DatabaseCredentials.java (63%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => database}/DatabaseDriver.java (89%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => database}/DatabasePoolProperties.java (77%) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => database}/DatabaseTimeoutsProperties.java (71%) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java similarity index 76% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 1d56ffca..f80409c2 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.database; public interface DatabaseConnectionProperties { DatabaseDriver driver(); diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseCredentials.java similarity index 63% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseCredentials.java index 910a46e1..83dee23f 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseCredentials.java @@ -1,3 +1,3 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.database; public record DatabaseCredentials(String username, String password) {} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java similarity index 89% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java index 68420bb5..200a62df 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseDriver.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.database; import lombok.AccessLevel; import lombok.Getter; diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java similarity index 77% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index e821d210..640581ec 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.database; import java.time.Duration; diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java similarity index 71% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java index 776d78ac..42df1915 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/DatabaseTimeoutsProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.database; public interface DatabaseTimeoutsProperties { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 14ee5747..dfddbe57 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -1,10 +1,10 @@ package javasabr.mqtt.auth.service.config; import javasabr.mqtt.auth.api.CredentialsSource; -import javasabr.mqtt.auth.api.DatabaseConnectionProperties; -import javasabr.mqtt.auth.api.DatabaseCredentials; -import javasabr.mqtt.auth.api.DatabasePoolProperties; -import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; +import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.database.DatabaseCredentials; +import javasabr.mqtt.auth.api.database.DatabasePoolProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; import javasabr.mqtt.auth.service.config.property.SpringDatabaseConnectionProperties; import javasabr.mqtt.auth.service.config.property.SpringDatabasePoolProperties; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java index b4f4b1a2..fbbd7ce4 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java @@ -1,10 +1,14 @@ package javasabr.mqtt.auth.service.config.property; -import javasabr.mqtt.auth.api.DatabaseConnectionProperties; -import javasabr.mqtt.auth.api.DatabaseDriver; +import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.database.DatabaseDriver; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication.credentials-source.database") -public record SpringDatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String name) implements - DatabaseConnectionProperties {} +public record SpringDatabaseConnectionProperties( + boolean enabled, + DatabaseDriver driver, + String host, + int port, + String name) implements DatabaseConnectionProperties {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java index 569af14e..7f62df53 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java @@ -1,7 +1,7 @@ package javasabr.mqtt.auth.service.config.property; import java.time.Duration; -import javasabr.mqtt.auth.api.DatabasePoolProperties; +import javasabr.mqtt.auth.api.database.DatabasePoolProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication.credentials-source.database.pool") diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java index 434f1ebb..9d132493 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java @@ -1,6 +1,6 @@ package javasabr.mqtt.auth.service.config.property; -import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication.credentials-source.database.timeout") diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index 2a6fe014..3eb8d675 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -1,6 +1,6 @@ package javasabr.mqtt.broker.application.service; -import javasabr.mqtt.auth.api.DatabaseCredentials; +import javasabr.mqtt.auth.api.database.DatabaseCredentials; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java index 60e8b679..0b050919 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java @@ -14,10 +14,10 @@ import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.Map; -import javasabr.mqtt.auth.api.DatabaseConnectionProperties; -import javasabr.mqtt.auth.api.DatabaseCredentials; -import javasabr.mqtt.auth.api.DatabasePoolProperties; -import javasabr.mqtt.auth.api.DatabaseTimeoutsProperties; +import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.database.DatabaseCredentials; +import javasabr.mqtt.auth.api.database.DatabasePoolProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import lombok.Builder; import org.flywaydb.core.Flyway; From 34adf8e28758daaf1bee31c3fe83a180e19eb94b Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:58:07 +0100 Subject: [PATCH 077/107] [broker-141] Refactoring of authentication properties structure --- .../src/main/resources/application.properties | 2 +- .../resources/application-test.properties | 4 +-- .../mqtt/auth/api/AuthenticationMethod.java | 3 ++- .../service/DefaultAuthenticationService.java | 26 +++++++++++++------ .../AuthenticationServiceSpringConfig.java | 10 +++---- .../AuthenticationMethodProperties.java | 7 +++++ .../property/AuthenticationProperties.java | 4 ++- .../service/AuthenticationProviderTest.groovy | 16 +++++++----- .../service/AuthenticationServiceTest.groovy | 3 ++- .../resources/application-test.properties | 1 - 10 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index 89c783ad..950928e0 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -1 +1 @@ -authentication.allow-anonymous=false +authentication.method.anonymous.enabled=false diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index f40a842c..34c1ab5b 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ -authentication.allow-anonymous=true -authentication.default-method=basic +authentication.method.default.type=basic +authentication.method.anonymous.enabled=true authentication.method.basic.enabled=true authentication.credentials-source.file.enabled=true authentication.credentials-source.file.path=classpath:auth/credentials-test diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index 28b4ea3e..b2e40953 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -21,7 +21,8 @@ public enum AuthenticationMethod { OAUTH2("oauth2"), BASIC("basic"), LDAP("ldap"), - ANONYMOUS("anon"); + ANONYMOUS("anonymous"), + DEFAULT("default"); private static final RefToRefDictionary CACHE = Arrays.stream(values()) .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationMethod::value, Function.identity())); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index e55e23f0..bc46cc82 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -10,6 +10,8 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; +import javasabr.mqtt.auth.service.config.property.AuthenticationMethodProperties; +import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; @@ -21,12 +23,13 @@ public class DefaultAuthenticationService implements AuthenticationService { Map availableProviders; - AuthenticationProvider defaultProvider; + @Nullable AuthenticationProvider defaultProvider; + AuthenticationProvider anonymousProvider; public DefaultAuthenticationService( List configuredProviders, - @Nullable AuthenticationMethod defaultMethod - ) { + AuthenticationProperties authenticationProperties) { + if (configuredProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } @@ -36,11 +39,18 @@ public DefaultAuthenticationService( Function.identity(), DefaultAuthenticationService::onDuplicateProviderErrorHandler, () -> new EnumMap<>(AuthenticationMethod.class))); - this.defaultProvider = availableProviders.get(defaultMethod == null ? AuthenticationMethod.BASIC : defaultMethod); - if (defaultProvider == null && !availableProviders.containsKey(AuthenticationMethod.ANONYMOUS)) { - throw new AuthenticationConfigException("None of [%s, BASIC, ANONYMOUS] authentication provider configured" - .formatted(defaultMethod)); + + this.anonymousProvider = availableProviders.get(AuthenticationMethod.ANONYMOUS); + AuthenticationMethodProperties defaultMethod = authenticationProperties.method().get(AuthenticationMethod.DEFAULT); + if (defaultMethod == null && anonymousProvider == null) { + throw new AuthenticationConfigException("Default authenticator method is not configured"); } + this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod.type()); + if (defaultProvider == null && anonymousProvider == null) { + throw new AuthenticationConfigException("Default [%s] authentication provider is not configured" + .formatted(defaultMethod.type())); + } + log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); } @@ -56,7 +66,7 @@ public Mono authenticate(MqttCredentials request) { AuthenticationMethod authenticationMethod = request.authenticationMethod(); AuthenticationProvider targetProvider = authenticationMethod == null ? defaultProvider : availableProviders.get(authenticationMethod); - return Mono.justOrEmpty(availableProviders.get(AuthenticationMethod.ANONYMOUS)) + return Mono.justOrEmpty(anonymousProvider) .flatMap(provider -> provider.authenticate(request)) .onErrorReturn(false) .filter(Boolean::booleanValue) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index f00de1fa..6c0d7462 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -1,13 +1,13 @@ package javasabr.mqtt.auth.service.config; import java.util.List; -import javasabr.mqtt.auth.api.AuthenticationMethod; import javasabr.mqtt.auth.api.AuthenticationProvider; import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; +import javasabr.mqtt.auth.service.config.property.AuthenticationMethodProperties; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.CustomLog; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -25,7 +25,8 @@ FileCredentialsSourceSpringConfig.class }) @EnableConfigurationProperties({ - AuthenticationProperties.class + AuthenticationProperties.class, + AuthenticationMethodProperties.class }) public class AuthenticationServiceSpringConfig { @@ -34,8 +35,7 @@ AuthenticationService authenticationService( List availableProviders, AuthenticationProperties authenticationProperties) { log.info("Initializing AuthenticationService..."); - AuthenticationMethod defaultAuthenticationMethod = authenticationProperties.defaultMethod(); - return new DefaultAuthenticationService(availableProviders, defaultAuthenticationMethod); + return new DefaultAuthenticationService(availableProviders, authenticationProperties); } @Bean @@ -47,7 +47,7 @@ AuthenticationProvider basicAuthenticationProvider(List confi } @Bean - @ConditionalOnProperty(name = "authentication.allow-anonymous", havingValue = "true") + @ConditionalOnProperty(name = "authentication.method.anonymous.enabled", havingValue = "true") AuthenticationProvider anonymousAuthenticationProvider() { return new AnonymousAuthenticationProvider(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java new file mode 100644 index 00000000..1870250f --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java @@ -0,0 +1,7 @@ +package javasabr.mqtt.auth.service.config.property; + +import javasabr.mqtt.auth.api.AuthenticationMethod; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("authentication.method.default") +public record AuthenticationMethodProperties(boolean enabled, AuthenticationMethod type) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index db20ee26..7bd4ec14 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -1,7 +1,9 @@ package javasabr.mqtt.auth.service.config.property; +import java.util.Map; import javasabr.mqtt.auth.api.AuthenticationMethod; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties(boolean allowAnonymous, AuthenticationMethod defaultMethod) {} +public record AuthenticationProperties(Map method) {} + diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 634599c1..b0eed4f7 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -25,9 +25,10 @@ class AuthenticationProviderTest extends IntegrationSpecification { List authenticationProviders @TestPropertySource(properties = [ - "authentication.allow-anonymous=false", + "authentication.method.anonymous.enabled=false", "authentication.method.basic.enabled=true", - "authentication.credentials-source.file.enabled=true" + "authentication.credentials-source.file.enabled=true", + "authentication.method.default.type=basic" ]) static class FileCredentialsSourceTest extends AuthenticationProviderTest { @Autowired @@ -45,9 +46,10 @@ class AuthenticationProviderTest extends IntegrationSpecification { } @TestPropertySource(properties = [ - "authentication.allow-anonymous=true", + "authentication.method.anonymous.enabled=true", "authentication.method.basic.enabled=true", - "authentication.credentials-source.file.enabled=true" + "authentication.credentials-source.file.enabled=true", + "authentication.method.default.type=basic" ]) static class AnonymousProviderTest extends AuthenticationProviderTest { @@ -63,7 +65,9 @@ class AuthenticationProviderTest extends IntegrationSpecification { } } - @TestPropertySource(properties = "authentication.allow-anonymous=true") + @TestPropertySource(properties = [ + "authentication.method.anonymous.enabled=true" + ]) static class AnonymousProvider2Test extends AuthenticationProviderTest { def "should create anonymous authentication provider"() { @@ -89,7 +93,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } when: appContext - .withPropertyValues("authentication.allow-anonymous=false") + .withPropertyValues("authentication.method.anonymous.enabled=false") .run({ context -> if (context.startupFailure) { throw context.startupFailure diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 2dd127b1..3f7804a9 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -18,8 +18,9 @@ import spock.lang.Shared import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ - "authentication.allow-anonymous=false", + "authentication.method.anonymous.enabled=false", "authentication.method.basic.enabled=true", + "authentication.method.default.type=basic", "authentication.credentials-source.file.enabled=true", "authentication.credentials-source.database.enabled=true" ]) diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index b50effaf..74daa8c8 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -1,4 +1,3 @@ -authentication.method.basic.enabled=true authentication.credentials-source.file.path=classpath:credentials/test authentication.credentials-source.database.driver=postgresql authentication.credentials-source.database.host=localhost From 6f39c74b29cacbbda7e4f84370561c9ae34280a6 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:06:12 +0100 Subject: [PATCH 078/107] [broker-141] Refactoring of authentication provider properties --- .../config/MqttBrokerSpringConfig.java | 2 +- .../src/main/resources/application.properties | 2 +- .../test/resources/application-test.properties | 6 +++--- .../service/DefaultAuthenticationService.java | 6 +++--- .../AuthenticationServiceSpringConfig.java | 4 ++-- .../property/AuthenticationMethodProperties.java | 4 ++-- .../property/AuthenticationProperties.java | 2 +- .../service/AuthenticationProviderTest.groovy | 16 ++++++++-------- .../service/AuthenticationServiceTest.groovy | 6 +++--- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 2f42c2ab..343337fd 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -111,7 +111,7 @@ MqttSessionService mqttSessionService( @Bean @ConditionalOnProperty( - name = "acl.engine.type", + name = "acl.engine.method", havingValue = "disabled", matchIfMissing = true) AuthorizationService authorizationService() { diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index 950928e0..8a4af680 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -1 +1 @@ -authentication.method.anonymous.enabled=false +authentication.provider.anonymous.enabled=false diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 34c1ab5b..5b47d83a 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,6 +1,6 @@ -authentication.method.default.type=basic -authentication.method.anonymous.enabled=true -authentication.method.basic.enabled=true +authentication.provider.default.method=basic +authentication.provider.anonymous.enabled=true +authentication.provider.basic.enabled=true authentication.credentials-source.file.enabled=true authentication.credentials-source.file.path=classpath:auth/credentials-test diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index bc46cc82..1d33dc5d 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -41,14 +41,14 @@ public DefaultAuthenticationService( () -> new EnumMap<>(AuthenticationMethod.class))); this.anonymousProvider = availableProviders.get(AuthenticationMethod.ANONYMOUS); - AuthenticationMethodProperties defaultMethod = authenticationProperties.method().get(AuthenticationMethod.DEFAULT); + AuthenticationMethodProperties defaultMethod = authenticationProperties.provider().get(AuthenticationMethod.DEFAULT); if (defaultMethod == null && anonymousProvider == null) { throw new AuthenticationConfigException("Default authenticator method is not configured"); } - this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod.type()); + this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod.method()); if (defaultProvider == null && anonymousProvider == null) { throw new AuthenticationConfigException("Default [%s] authentication provider is not configured" - .formatted(defaultMethod.type())); + .formatted(defaultMethod.method())); } log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 6c0d7462..b5ce0e85 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -40,14 +40,14 @@ AuthenticationService authenticationService( @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") - @ConditionalOnProperty(name = "authentication.method.basic.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") @ConditionalOnBean(CredentialsSource.class) AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { return new BasicAuthenticationProvider(configuredCredentialsSources); } @Bean - @ConditionalOnProperty(name = "authentication.method.anonymous.enabled", havingValue = "true") + @ConditionalOnProperty(name = "authentication.provider.anonymous.enabled", havingValue = "true") AuthenticationProvider anonymousAuthenticationProvider() { return new AnonymousAuthenticationProvider(); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java index 1870250f..2c7ea415 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java @@ -3,5 +3,5 @@ import javasabr.mqtt.auth.api.AuthenticationMethod; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("authentication.method.default") -public record AuthenticationMethodProperties(boolean enabled, AuthenticationMethod type) {} +@ConfigurationProperties("authentication.provider.default") +public record AuthenticationMethodProperties(boolean enabled, AuthenticationMethod method) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index 7bd4ec14..d3aa5a65 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -5,5 +5,5 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties(Map method) {} +public record AuthenticationProperties(Map provider) {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index b0eed4f7..274e75bf 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -25,10 +25,10 @@ class AuthenticationProviderTest extends IntegrationSpecification { List authenticationProviders @TestPropertySource(properties = [ - "authentication.method.anonymous.enabled=false", - "authentication.method.basic.enabled=true", + "authentication.provider.anonymous.enabled=false", + "authentication.provider.basic.enabled=true", "authentication.credentials-source.file.enabled=true", - "authentication.method.default.type=basic" + "authentication.provider.default.method=basic" ]) static class FileCredentialsSourceTest extends AuthenticationProviderTest { @Autowired @@ -46,10 +46,10 @@ class AuthenticationProviderTest extends IntegrationSpecification { } @TestPropertySource(properties = [ - "authentication.method.anonymous.enabled=true", - "authentication.method.basic.enabled=true", + "authentication.provider.anonymous.enabled=true", + "authentication.provider.basic.enabled=true", "authentication.credentials-source.file.enabled=true", - "authentication.method.default.type=basic" + "authentication.provider.default.method=basic" ]) static class AnonymousProviderTest extends AuthenticationProviderTest { @@ -66,7 +66,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } @TestPropertySource(properties = [ - "authentication.method.anonymous.enabled=true" + "authentication.provider.anonymous.enabled=true" ]) static class AnonymousProvider2Test extends AuthenticationProviderTest { @@ -93,7 +93,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } when: appContext - .withPropertyValues("authentication.method.anonymous.enabled=false") + .withPropertyValues("authentication.provider.anonymous.enabled=false") .run({ context -> if (context.startupFailure) { throw context.startupFailure diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 3f7804a9..500ff865 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -18,9 +18,9 @@ import spock.lang.Shared import java.nio.charset.StandardCharsets @TestPropertySource(properties = [ - "authentication.method.anonymous.enabled=false", - "authentication.method.basic.enabled=true", - "authentication.method.default.type=basic", + "authentication.provider.anonymous.enabled=false", + "authentication.provider.basic.enabled=true", + "authentication.provider.default.method=basic", "authentication.credentials-source.file.enabled=true", "authentication.credentials-source.database.enabled=true" ]) From 2bb41d99b6c51b48d5409ae961857db9ed62d29f Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:12:45 +0100 Subject: [PATCH 079/107] [broker-141] Refactoring of default provider properties --- .../auth/service/DefaultAuthenticationService.java | 10 +++++----- .../config/AuthenticationServiceSpringConfig.java | 8 ++++---- .../config/property/AuthenticationProperties.java | 2 +- ...dProperties.java => DefaultProviderProperties.java} | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/{AuthenticationMethodProperties.java => DefaultProviderProperties.java} (72%) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 1d33dc5d..4a236586 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -10,7 +10,7 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -import javasabr.mqtt.auth.service.config.property.AuthenticationMethodProperties; +import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.AccessLevel; import lombok.CustomLog; @@ -28,7 +28,7 @@ public class DefaultAuthenticationService implements AuthenticationService { public DefaultAuthenticationService( List configuredProviders, - AuthenticationProperties authenticationProperties) { + DefaultProviderProperties defaultProviderProperties) { if (configuredProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); @@ -41,14 +41,14 @@ public DefaultAuthenticationService( () -> new EnumMap<>(AuthenticationMethod.class))); this.anonymousProvider = availableProviders.get(AuthenticationMethod.ANONYMOUS); - AuthenticationMethodProperties defaultMethod = authenticationProperties.provider().get(AuthenticationMethod.DEFAULT); + AuthenticationMethod defaultMethod = defaultProviderProperties.method(); if (defaultMethod == null && anonymousProvider == null) { throw new AuthenticationConfigException("Default authenticator method is not configured"); } - this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod.method()); + this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod); if (defaultProvider == null && anonymousProvider == null) { throw new AuthenticationConfigException("Default [%s] authentication provider is not configured" - .formatted(defaultMethod.method())); + .formatted(defaultMethod)); } log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index b5ce0e85..4103b117 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -7,7 +7,7 @@ import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.auth.service.config.property.AuthenticationMethodProperties; +import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.CustomLog; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -26,16 +26,16 @@ }) @EnableConfigurationProperties({ AuthenticationProperties.class, - AuthenticationMethodProperties.class + DefaultProviderProperties.class }) public class AuthenticationServiceSpringConfig { @Bean AuthenticationService authenticationService( List availableProviders, - AuthenticationProperties authenticationProperties) { + DefaultProviderProperties defaultProviderProperties) { log.info("Initializing AuthenticationService..."); - return new DefaultAuthenticationService(availableProviders, authenticationProperties); + return new DefaultAuthenticationService(availableProviders, defaultProviderProperties); } @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java index d3aa5a65..48768fae 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java @@ -5,5 +5,5 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties(Map provider) {} +public record AuthenticationProperties(Map provider) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java similarity index 72% rename from authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java rename to authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java index 2c7ea415..70e979e7 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationMethodProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java @@ -4,4 +4,4 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("authentication.provider.default") -public record AuthenticationMethodProperties(boolean enabled, AuthenticationMethod method) {} +public record DefaultProviderProperties(boolean enabled, AuthenticationMethod method) {} From e59e6e3d378989e0f603a96f7bca663cc928e721 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:31:28 +0100 Subject: [PATCH 080/107] [broker-141] Revert unnecessary changes --- .../application/config/MqttBrokerSpringConfig.java | 2 +- .../javasabr/mqtt/auth/api/AuthenticationMethod.java | 3 +-- .../mqtt/auth/service/DefaultAuthenticationService.java | 1 - .../config/AuthenticationServiceSpringConfig.java | 6 +----- .../config/property/AuthenticationProperties.java | 9 --------- .../config/property/DefaultProviderProperties.java | 2 +- 6 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 343337fd..25e3b7ae 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -111,7 +111,7 @@ MqttSessionService mqttSessionService( @Bean @ConditionalOnProperty( - name = "acl.engine.method", + name = "acl.engine.type", havingValue = "disabled", matchIfMissing = true) AuthorizationService authorizationService() { diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index b2e40953..810e9539 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -21,8 +21,7 @@ public enum AuthenticationMethod { OAUTH2("oauth2"), BASIC("basic"), LDAP("ldap"), - ANONYMOUS("anonymous"), - DEFAULT("default"); + ANONYMOUS("anonymous"); private static final RefToRefDictionary CACHE = Arrays.stream(values()) .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationMethod::value, Function.identity())); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 4a236586..b253430a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -11,7 +11,6 @@ import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 4103b117..797d29b2 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -8,7 +8,6 @@ import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; -import javasabr.mqtt.auth.service.config.property.AuthenticationProperties; import lombok.CustomLog; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -24,10 +23,7 @@ DatabaseCredentialsSourceSpringConfig.class, FileCredentialsSourceSpringConfig.class }) -@EnableConfigurationProperties({ - AuthenticationProperties.class, - DefaultProviderProperties.class -}) +@EnableConfigurationProperties(DefaultProviderProperties.class) public class AuthenticationServiceSpringConfig { @Bean diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java deleted file mode 100644 index 48768fae..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/AuthenticationProperties.java +++ /dev/null @@ -1,9 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.util.Map; -import javasabr.mqtt.auth.api.AuthenticationMethod; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "authentication") -public record AuthenticationProperties(Map provider) {} - diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java index 70e979e7..a2d10f9b 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java @@ -4,4 +4,4 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("authentication.provider.default") -public record DefaultProviderProperties(boolean enabled, AuthenticationMethod method) {} +public record DefaultProviderProperties(AuthenticationMethod method) {} From 2bdee9c3d01a2043c0f393e0501368e52cb12e6f Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:48:39 +0100 Subject: [PATCH 081/107] [broker-141] Add error handlers to DefaultAuthenticationService --- .../service/DefaultAuthenticationService.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index b253430a..0331c983 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -21,6 +21,19 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { + private static Mono onAnonymousProviderErrorHandler(Throwable exception) { + log.error("Anonymous authentication provider threw an error: %s".formatted(exception.getMessage())); + return Mono.just(false); + } + + private static Mono onAuthenticationProviderErrorHandler( + @Nullable AuthenticationProvider provider, + Throwable exception) { + String authenticationMethod = provider == null ? null : provider.getAuthenticationMethod().value(); + log.error("%s authentication provider threw an error: %s".formatted(authenticationMethod, exception.getMessage())); + return Mono.just(false); + } + Map availableProviders; @Nullable AuthenticationProvider defaultProvider; AuthenticationProvider anonymousProvider; @@ -67,11 +80,11 @@ public Mono authenticate(MqttCredentials request) { authenticationMethod == null ? defaultProvider : availableProviders.get(authenticationMethod); return Mono.justOrEmpty(anonymousProvider) .flatMap(provider -> provider.authenticate(request)) - .onErrorReturn(false) + .onErrorResume(DefaultAuthenticationService::onAnonymousProviderErrorHandler) .filter(Boolean::booleanValue) .switchIfEmpty(Mono.justOrEmpty(targetProvider) .flatMap(provider -> provider.authenticate(request)) - .onErrorReturn(false) + .onErrorResume(exception -> onAuthenticationProviderErrorHandler(targetProvider, exception)) .defaultIfEmpty(false)); } From 7d14448ae99bdab01602903da29374f78c7133a4 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:03:01 +0100 Subject: [PATCH 082/107] [broker-141] Add test --- .../service/DefaultAuthenticationService.java | 2 +- .../service/AuthenticationProviderTest.groovy | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 0331c983..43536409 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -60,7 +60,7 @@ public DefaultAuthenticationService( this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod); if (defaultProvider == null && anonymousProvider == null) { throw new AuthenticationConfigException("Default [%s] authentication provider is not configured" - .formatted(defaultMethod)); + .formatted(defaultMethod.value())); } log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 274e75bf..a7320c35 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -81,7 +81,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { static class EmptyProviderTest extends Specification { - def "should fail start application context without any authentication provider"() { + def "should fail start application context without any authentication provider"(String[] properties) { given: PropertySource propertySource = new PropertiesPropertySourceLoader() .load("test-props", new ClassPathResource("application-test.properties")).getFirst() @@ -93,7 +93,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } when: appContext - .withPropertyValues("authentication.provider.anonymous.enabled=false") + .withPropertyValues(properties) .run({ context -> if (context.startupFailure) { throw context.startupFailure @@ -103,8 +103,24 @@ class AuthenticationProviderTest extends IntegrationSpecification { def exception = thrown(Exception) with(rootCauseOf(exception)) { rootCause -> assert rootCause instanceof AuthenticationConfigException - assert message == "Authenticator providers are not configured" + assert message == errorMessage } + where: + properties | errorMessage + [ + "authentication.provider.anonymous.enabled=false" + ] | "Authenticator providers are not configured" + [ + "authentication.provider.anonymous.enabled=false", + "authentication.provider.basic.enabled=true", + "authentication.credentials-source.file.enabled=true" + ] | "Default authenticator method is not configured" + [ + "authentication.provider.default.method=jwt", + "authentication.provider.anonymous.enabled=false", + "authentication.provider.basic.enabled=true", + "authentication.credentials-source.file.enabled=true" + ] | "Default [jwt] authentication provider is not configured" } } From 9a64374bc9280511f7f874817725b89a8900a3fc Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:15:02 +0100 Subject: [PATCH 083/107] [broker-141] Rework DefaultAuthenticationService according review comments --- .../mqtt/auth/api/AuthenticationMethod.java | 13 ++- .../mqtt/auth/api/AuthenticationProvider.java | 2 + .../mqtt/auth/api/MqttCredentials.java | 14 +++- .../provider/BasicAuthenticationProvider.java | 20 ++++- .../AnonymousAuthenticationProvider.java | 25 ------ .../service/DefaultAuthenticationService.java | 82 +++++++++---------- .../AuthenticationServiceSpringConfig.java | 15 +--- .../property/DefaultProviderProperties.java | 7 -- .../service/AuthenticationProviderTest.groovy | 58 +------------ .../service/AuthenticationServiceTest.groovy | 8 +- 10 files changed, 84 insertions(+), 160 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index 810e9539..c265764d 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -15,18 +15,17 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum AuthenticationMethod { - X509("x509"), - IP_CIDR("cidr"), - JWT("jwt"), - OAUTH2("oauth2"), - BASIC("basic"), - LDAP("ldap"), - ANONYMOUS("anonymous"); + X509("x509", 1), + JWT("jwt", 3), + OAUTH2("oauth2", 4), + BASIC("basic", 5), + LDAP("ldap", 6); private static final RefToRefDictionary CACHE = Arrays.stream(values()) .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationMethod::value, Function.identity())); String value; + int priority; public static AuthenticationMethod fromValue(String value) { return CACHE.get(value); diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java index 7950b703..855b9b5e 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationProvider.java @@ -6,5 +6,7 @@ public interface AuthenticationProvider { AuthenticationMethod getAuthenticationMethod(); + boolean supports(MqttCredentials credentials); + Mono authenticate(MqttCredentials credentials); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index c238b190..ee5d1581 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -1,7 +1,19 @@ package javasabr.mqtt.auth.api; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.StringUtils; + public record MqttCredentials( String username, byte[] password, AuthenticationMethod authenticationMethod, - byte[] authenticationData) {} + byte[] authenticationData) { + + public boolean isAnonymous() { + return StringUtils.isEmpty(username) && ArrayUtils.isEmpty(password); + } + + public boolean isMethodDefined() { + return authenticationMethod != null; + } +} diff --git a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java index b45dca3a..af0a07ae 100644 --- a/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java +++ b/authentication-provider-basic/src/main/java/javasabr/mqtt/auth/provider/BasicAuthenticationProvider.java @@ -9,15 +9,19 @@ import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayCollectors; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class BasicAuthenticationProvider implements AuthenticationProvider { + private static final byte MIN_PRINTABLE_ASCII = ' '; // 32 + private static final byte MAX_PRINTABLE_ASCII = '~'; // 126 + Array credentialsSources; public BasicAuthenticationProvider(List credentialsSources) { @@ -44,4 +48,18 @@ public String toString() { getAuthenticationMethod(), credentialsSources); } + + @Override + public boolean supports(MqttCredentials credentials) { + byte[] password = credentials.password(); + if (StringUtils.isEmpty(credentials.username()) || ArrayUtils.isEmpty(password)) { + return false; + } + for (byte charByte : password) { + if (charByte < MIN_PRINTABLE_ASCII || charByte > MAX_PRINTABLE_ASCII) { + return false; + } + } + return true; + } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java deleted file mode 100644 index ae527143..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/AnonymousAuthenticationProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package javasabr.mqtt.auth.service; - -import javasabr.mqtt.auth.api.AuthenticationMethod; -import javasabr.mqtt.auth.api.AuthenticationProvider; -import javasabr.mqtt.auth.api.MqttCredentials; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.StringUtils; -import reactor.core.publisher.Mono; - -public class AnonymousAuthenticationProvider implements AuthenticationProvider { - @Override - public AuthenticationMethod getAuthenticationMethod() { - return AuthenticationMethod.ANONYMOUS; - } - - @Override - public Mono authenticate(MqttCredentials credentials) { - return Mono.just(StringUtils.isEmpty(credentials.username()) && ArrayUtils.isEmpty(credentials.password())); - } - - @Override - public String toString() { - return "{ \"authenticationMethod\": \"%s\", \"enabled\": true }".formatted(getAuthenticationMethod()); - } -} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 43536409..393e6d48 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -1,5 +1,6 @@ package javasabr.mqtt.auth.service; +import java.util.Comparator; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -10,60 +11,58 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayCollectors; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @CustomLog @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - private static Mono onAnonymousProviderErrorHandler(Throwable exception) { - log.error("Anonymous authentication provider threw an error: %s".formatted(exception.getMessage())); - return Mono.just(false); - } - - private static Mono onAuthenticationProviderErrorHandler( - @Nullable AuthenticationProvider provider, - Throwable exception) { - String authenticationMethod = provider == null ? null : provider.getAuthenticationMethod().value(); - log.error("%s authentication provider threw an error: %s".formatted(authenticationMethod, exception.getMessage())); - return Mono.just(false); - } - - Map availableProviders; - @Nullable AuthenticationProvider defaultProvider; - AuthenticationProvider anonymousProvider; - - public DefaultAuthenticationService( - List configuredProviders, - DefaultProviderProperties defaultProviderProperties) { + Map availableProvidersMap; + Array availableProvidersArray; + boolean allowAnonymous; + public DefaultAuthenticationService(List configuredProviders, boolean allowAnonymous) { if (configuredProviders.isEmpty()) { throw new AuthenticationConfigException("Authenticator providers are not configured"); } - this.availableProviders = configuredProviders.stream() + this.allowAnonymous = allowAnonymous; + this.availableProvidersMap = configuredProviders.stream() .collect(Collectors.toMap( AuthenticationProvider::getAuthenticationMethod, Function.identity(), DefaultAuthenticationService::onDuplicateProviderErrorHandler, () -> new EnumMap<>(AuthenticationMethod.class))); + this.availableProvidersArray = configuredProviders.stream() + .sorted(Comparator.comparingInt(provider -> provider.getAuthenticationMethod().priority())) + .collect(ArrayCollectors.toArray(AuthenticationProvider.class)); + log.info(this.availableProvidersMap, DefaultAuthenticationService::buildServiceDescription); + } - this.anonymousProvider = availableProviders.get(AuthenticationMethod.ANONYMOUS); - AuthenticationMethod defaultMethod = defaultProviderProperties.method(); - if (defaultMethod == null && anonymousProvider == null) { - throw new AuthenticationConfigException("Default authenticator method is not configured"); - } - this.defaultProvider = defaultMethod == null ? null : availableProviders.get(defaultMethod); - if (defaultProvider == null && anonymousProvider == null) { - throw new AuthenticationConfigException("Default [%s] authentication provider is not configured" - .formatted(defaultMethod.value())); + @Override + public Mono authenticate(MqttCredentials mqttCredentials) { + if (mqttCredentials.isAnonymous()) { + return Mono.just(allowAnonymous); + } else if (mqttCredentials.isMethodDefined()) { + AuthenticationProvider provider = availableProvidersMap.get(mqttCredentials.authenticationMethod()); + return provider == null ? Mono.just(false) : authenticateSafe(provider, mqttCredentials); + } else { + return Flux.fromIterable(availableProvidersArray) + .filter(provider -> provider.supports(mqttCredentials)) + .concatMap(provider -> authenticateSafe(provider, mqttCredentials)) + .any(Boolean::booleanValue); } + } - log.info(this.availableProviders, DefaultAuthenticationService::buildServiceDescription); + private Mono authenticateSafe(AuthenticationProvider provider, MqttCredentials request) { + return provider.authenticate(request) + .onErrorResume(exception -> onAuthenticationProviderErrorHandler(provider, exception)); } private static AuthenticationProvider onDuplicateProviderErrorHandler( @@ -73,19 +72,12 @@ private static AuthenticationProvider onDuplicateProviderErrorHandler( .formatted(first.getAuthenticationMethod())); } - @Override - public Mono authenticate(MqttCredentials request) { - AuthenticationMethod authenticationMethod = request.authenticationMethod(); - AuthenticationProvider targetProvider = - authenticationMethod == null ? defaultProvider : availableProviders.get(authenticationMethod); - return Mono.justOrEmpty(anonymousProvider) - .flatMap(provider -> provider.authenticate(request)) - .onErrorResume(DefaultAuthenticationService::onAnonymousProviderErrorHandler) - .filter(Boolean::booleanValue) - .switchIfEmpty(Mono.justOrEmpty(targetProvider) - .flatMap(provider -> provider.authenticate(request)) - .onErrorResume(exception -> onAuthenticationProviderErrorHandler(targetProvider, exception)) - .defaultIfEmpty(false)); + private static Mono onAuthenticationProviderErrorHandler( + @Nullable AuthenticationProvider provider, + Throwable exception) { + String authenticationMethod = provider == null ? null : provider.getAuthenticationMethod().value(); + log.error("%s authentication provider threw an error: %s".formatted(authenticationMethod, exception.getMessage())); + return Mono.just(false); } private static String buildServiceDescription(Map providers) { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 797d29b2..7e12d63e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -5,14 +5,12 @@ import javasabr.mqtt.auth.api.AuthenticationService; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; -import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; -import javasabr.mqtt.auth.service.config.property.DefaultProviderProperties; import lombok.CustomLog; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -23,15 +21,14 @@ DatabaseCredentialsSourceSpringConfig.class, FileCredentialsSourceSpringConfig.class }) -@EnableConfigurationProperties(DefaultProviderProperties.class) public class AuthenticationServiceSpringConfig { @Bean AuthenticationService authenticationService( List availableProviders, - DefaultProviderProperties defaultProviderProperties) { + @Value("${authentication.provider.anonymous.enabled}") boolean allowAnonymous) { log.info("Initializing AuthenticationService..."); - return new DefaultAuthenticationService(availableProviders, defaultProviderProperties); + return new DefaultAuthenticationService(availableProviders, allowAnonymous); } @Bean @@ -41,10 +38,4 @@ AuthenticationService authenticationService( AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { return new BasicAuthenticationProvider(configuredCredentialsSources); } - - @Bean - @ConditionalOnProperty(name = "authentication.provider.anonymous.enabled", havingValue = "true") - AuthenticationProvider anonymousAuthenticationProvider() { - return new AnonymousAuthenticationProvider(); - } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java deleted file mode 100644 index a2d10f9b..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/DefaultProviderProperties.java +++ /dev/null @@ -1,7 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import javasabr.mqtt.auth.api.AuthenticationMethod; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("authentication.provider.default") -public record DefaultProviderProperties(AuthenticationMethod method) {} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index a7320c35..2af72762 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -6,7 +6,6 @@ import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.exception.AuthenticationConfigException import javasabr.mqtt.auth.credentials.source.FileCredentialsSource import javasabr.mqtt.auth.provider.BasicAuthenticationProvider -import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.env.PropertiesPropertySourceLoader @@ -16,7 +15,6 @@ import org.springframework.core.io.ClassPathResource import org.springframework.test.context.TestPropertySource import spock.lang.Specification -import static javasabr.mqtt.auth.api.AuthenticationMethod.ANONYMOUS import static javasabr.mqtt.auth.api.AuthenticationMethod.BASIC class AuthenticationProviderTest extends IntegrationSpecification { @@ -45,43 +43,9 @@ class AuthenticationProviderTest extends IntegrationSpecification { } } - @TestPropertySource(properties = [ - "authentication.provider.anonymous.enabled=true", - "authentication.provider.basic.enabled=true", - "authentication.credentials-source.file.enabled=true", - "authentication.provider.default.method=basic" - ]) - static class AnonymousProviderTest extends AuthenticationProviderTest { - - def "should create file credentials source and basic authentication provider"() { - expect: - authenticationProviders.any { provider -> - provider.authenticationMethod == ANONYMOUS && provider instanceof AnonymousAuthenticationProvider - } - and: - authenticationProviders.any { provider -> - provider.authenticationMethod == BASIC && provider instanceof BasicAuthenticationProvider - } - } - } - - @TestPropertySource(properties = [ - "authentication.provider.anonymous.enabled=true" - ]) - static class AnonymousProvider2Test extends AuthenticationProviderTest { - - def "should create anonymous authentication provider"() { - expect: - verifyEach(authenticationProviders) { provider -> - provider.authenticationMethod == ANONYMOUS - provider instanceof AnonymousAuthenticationProvider - } - } - } - static class EmptyProviderTest extends Specification { - def "should fail start application context without any authentication provider"(String[] properties) { + def "should fail start application context without any authentication provider"() { given: PropertySource propertySource = new PropertiesPropertySourceLoader() .load("test-props", new ClassPathResource("application-test.properties")).getFirst() @@ -93,7 +57,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } when: appContext - .withPropertyValues(properties) + .withPropertyValues("authentication.provider.anonymous.enabled=false") .run({ context -> if (context.startupFailure) { throw context.startupFailure @@ -103,24 +67,8 @@ class AuthenticationProviderTest extends IntegrationSpecification { def exception = thrown(Exception) with(rootCauseOf(exception)) { rootCause -> assert rootCause instanceof AuthenticationConfigException - assert message == errorMessage + assert message == "Authenticator providers are not configured" } - where: - properties | errorMessage - [ - "authentication.provider.anonymous.enabled=false" - ] | "Authenticator providers are not configured" - [ - "authentication.provider.anonymous.enabled=false", - "authentication.provider.basic.enabled=true", - "authentication.credentials-source.file.enabled=true" - ] | "Default authenticator method is not configured" - [ - "authentication.provider.default.method=jwt", - "authentication.provider.anonymous.enabled=false", - "authentication.provider.basic.enabled=true", - "authentication.credentials-source.file.enabled=true" - ] | "Default [jwt] authentication provider is not configured" } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 500ff865..27f88265 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -1,12 +1,11 @@ package javasabr.mqtt.broker.application.service -import javasabr.mqtt.auth.api.AuthenticationMethod + import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.CredentialsSourceType import javasabr.mqtt.auth.api.MqttCredentials -import javasabr.mqtt.auth.service.AnonymousAuthenticationProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource @@ -74,10 +73,5 @@ class AuthenticationServiceTest extends IntegrationSpecification { expectedSourceTypes.remove(credentialsSource.type) } expectedSourceTypes.isEmpty() - and: - verifyEach(authenticationProviders) { provider -> - provider.authenticationMethod != AuthenticationMethod.ANONYMOUS - !(provider instanceof AnonymousAuthenticationProvider) - } } } From 3d30649c5e60e6985773ccc4441c14eab78bd76c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:28:15 +0100 Subject: [PATCH 084/107] [broker-141] Rework Database Properties --- .../config/MqttBrokerSpringConfig.java | 2 +- .../DatabaseConnectionProperties.java | 10 +--- .../api/database/DatabasePoolProperties.java | 9 +--- .../database/DatabaseTimeoutsProperties.java | 7 +-- .../AuthenticationServiceSpringConfig.java | 2 - ...DatabaseCredentialsSourceSpringConfig.java | 47 +++++++++++++++---- .../SpringDatabaseConnectionProperties.java | 14 ------ .../SpringDatabasePoolProperties.java | 10 ---- .../SpringDatabaseTimeoutsProperties.java | 9 ---- .../service/impl/DefaultTopicService.java | 2 +- .../DatabaseCredentialsSourceFactories.java | 4 +- 11 files changed, 44 insertions(+), 72 deletions(-) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 9eac4af9..24d2fe16 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -361,7 +361,7 @@ ServerNetworkConfig externalNetworkConfig( @Value("${mqtt.external.network.read.buffer.size:512}") int readBufferSize, @Value("${mqtt.external.network.pending.buffer.size:1024}") int pendingBufferSize, @Value("${mqtt.external.network.write.buffer.size:512}") int writeBufferSize, - @Value("${mqtt.external.network.thread.group.name:ExternalNetwork}") String threadGroupName, + @Value("${mqtt.external.network.thread.group.dbName:ExternalNetwork}") String threadGroupName, @Value("${mqtt.external.network.thread.count:1}") int threadGroupMaxSize) { return ServerNetworkConfig.SimpleServerNetworkConfig .builder() diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index f80409c2..50d7551c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,12 +1,4 @@ package javasabr.mqtt.auth.api.database; -public interface DatabaseConnectionProperties { - DatabaseDriver driver(); - - String host(); - - int port(); - - String name(); -} +public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) {} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index 640581ec..0432e516 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -2,12 +2,5 @@ import java.time.Duration; -public interface DatabasePoolProperties { - - Duration maxIdleTime(); - - int initialSize(); - - int maxSize(); -} +public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) {} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java index 42df1915..c2c5dcba 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java @@ -1,9 +1,4 @@ package javasabr.mqtt.auth.api.database; -public interface DatabaseTimeoutsProperties { - - String lockTimeout(); - - String statementTimeout(); -} +public record DatabaseTimeoutsProperties(String lockTimeout, String statementTimeout) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 7e12d63e..4af84f5e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -8,7 +8,6 @@ import javasabr.mqtt.auth.service.DefaultAuthenticationService; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -34,7 +33,6 @@ AuthenticationService authenticationService( @Bean @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") - @ConditionalOnBean(CredentialsSource.class) AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { return new BasicAuthenticationProvider(configuredCredentialsSources); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index dfddbe57..c77d0daf 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -1,32 +1,59 @@ package javasabr.mqtt.auth.service.config; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; +import javasabr.mqtt.auth.api.database.DatabaseDriver; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; -import javasabr.mqtt.auth.service.config.property.SpringDatabaseConnectionProperties; -import javasabr.mqtt.auth.service.config.property.SpringDatabasePoolProperties; -import javasabr.mqtt.auth.service.config.property.SpringDatabaseTimeoutsProperties; import org.flywaydb.core.Flyway; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.boot.convert.DurationUnit; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +import org.springframework.core.convert.ConversionService; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") -@EnableConfigurationProperties({ - SpringDatabasePoolProperties.class, - SpringDatabaseTimeoutsProperties.class, - SpringDatabaseConnectionProperties.class -}) public class DatabaseCredentialsSourceSpringConfig { + @Bean + public ConversionService conversionService() { + return new ApplicationConversionService(); + } + + @Bean + DatabasePoolProperties databasePoolPropertiesRecord( + @Value("${authentication.credentials-source.database.pool.max-idle-time}") @DurationUnit(ChronoUnit.MINUTES) Duration maxIdleTime, + @Value("${authentication.credentials-source.database.pool.initial-size}") int initialSize, + @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize){ + return new DatabasePoolProperties(maxIdleTime, initialSize, maxSize); + } + + @Bean + DatabaseConnectionProperties databaseConnectionPropertiesRecord( + @Value("${authentication.credentials-source.database.driver}") DatabaseDriver driver, + @Value("${authentication.credentials-source.database.host}") String host, + @Value("${authentication.credentials-source.database.port}") int port, + @Value("${authentication.credentials-source.database.name}") String name){ + return new DatabaseConnectionProperties(driver, host, port, name); + } + + @Bean + DatabaseTimeoutsProperties databaseTimeoutsPropertiesRecord( + @Value("${authentication.credentials-source.database.timeout.lock-timeout}") String lockTimeout, + @Value("${authentication.credentials-source.database.timeout.statement-timeout}") String statementTimeout){ + return new DatabaseTimeoutsProperties(lockTimeout, statementTimeout); + } + @Bean @DependsOn("flyway") CredentialsSource dbCredentialsSource( @@ -44,7 +71,7 @@ CredentialsSource dbCredentialsSource( @Bean(initMethod = "migrate") Flyway flyway( - SpringDatabaseConnectionProperties databaseCredentialsSourceProperties, + DatabaseConnectionProperties databaseCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { return DatabaseCredentialsSourceFactories.flyway() .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java deleted file mode 100644 index fbbd7ce4..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseConnectionProperties.java +++ /dev/null @@ -1,14 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; -import javasabr.mqtt.auth.api.database.DatabaseDriver; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "authentication.credentials-source.database") -public record SpringDatabaseConnectionProperties( - boolean enabled, - DatabaseDriver driver, - String host, - int port, - String name) implements DatabaseConnectionProperties {} - diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java deleted file mode 100644 index 7f62df53..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabasePoolProperties.java +++ /dev/null @@ -1,10 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.time.Duration; -import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "authentication.credentials-source.database.pool") -public record SpringDatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) implements - DatabasePoolProperties {} - diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java deleted file mode 100644 index 9d132493..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringDatabaseTimeoutsProperties.java +++ /dev/null @@ -1,9 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "authentication.credentials-source.database.timeout") -public record SpringDatabaseTimeoutsProperties(String lockTimeout, String statementTimeout) implements - DatabaseTimeoutsProperties {} - diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java index c4981b89..68b31221 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java @@ -39,7 +39,7 @@ public boolean isValidTopicFilter(NetworkMqttUser user, String rawTopicFilter) { @Override public TopicName createTopicName(NetworkMqttUser user, String rawTopicName) { if (!TopicValidator.validateTopicName(rawTopicName)) { - log.warning(user.clientId(), rawTopicName, "[%s] Invalid topic name:[%s]"::formatted); + log.warning(user.clientId(), rawTopicName, "[%s] Invalid topic dbName:[%s]"::formatted); return TopicName.INVALID_TOPIC_NAME; } return TopicName.valueOf(rawTopicName); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java index 0b050919..c486cbb1 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java @@ -32,7 +32,7 @@ private static Flyway createFlyway( databaseCredentialsSourceProperties.driver().value(), databaseCredentialsSourceProperties.host(), databaseCredentialsSourceProperties.port(), - databaseCredentialsSourceProperties.name()); + databaseCredentialsSourceProperties.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); @@ -48,7 +48,7 @@ private static DatabaseCredentialsSource createDatabaseCredentialsSource( "lock_timeout", databaseTimeoutsProperties.lockTimeout(), "statement_timeout", databaseTimeoutsProperties.statementTimeout()); ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() - .option(DATABASE, databaseConnectionProperties.name()) + .option(DATABASE, databaseConnectionProperties.dbName()) .option(DRIVER, databaseConnectionProperties.driver().value()) .option(HOST, databaseConnectionProperties.host()) .option(PORT, databaseConnectionProperties.port()) From b43b2b623424b57c590269d314547cd19476d4ee Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:19:26 +0100 Subject: [PATCH 085/107] [broker-141] Improve authentication service properties --- .../mqtt/auth/api/FileProperties.java | 8 ----- .../DatabaseConnectionProperties.java | 25 +++++++++++++- .../auth/api/database/DatabaseDriver.java | 12 +++++++ .../api/database/DatabasePoolProperties.java | 18 ++++++++-- .../auth/api/database/DatabaseTimeouts.java | 18 ++++++++++ .../database/DatabaseTimeoutsProperties.java | 4 --- .../mqtt/auth/api/file/FileProperties.java | 12 +++++++ .../{ => file}/InMemoryCredentialsSource.java | 4 ++- ...DatabaseCredentialsSourceSpringConfig.java | 34 +++++++------------ .../FileCredentialsSourceSpringConfig.java | 13 ++++--- .../config/property/SpringFileProperties.java | 8 ----- .../resources/application-test.properties | 6 ++-- .../DatabaseCredentialsSourceFactories.java | 4 +-- .../source/FileCredentialsSource.java | 2 +- 14 files changed, 112 insertions(+), 56 deletions(-) delete mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java delete mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java rename authentication-api/src/main/java/javasabr/mqtt/auth/api/{ => file}/InMemoryCredentialsSource.java (93%) delete mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java deleted file mode 100644 index 912cad2e..00000000 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/FileProperties.java +++ /dev/null @@ -1,8 +0,0 @@ -package javasabr.mqtt.auth.api; - -import java.net.URI; - -public interface FileProperties { - - URI path(); -} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 50d7551c..0a1fd0f4 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,4 +1,27 @@ package javasabr.mqtt.auth.api.database; -public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) {} +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; +import javasabr.rlib.common.util.StringUtils; +public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) { + + public DatabaseConnectionProperties(String driver, String host, int port, String dbName) { + if (StringUtils.isEmpty(host)) { + throw new AuthenticationConfigException("Database host is not specified"); + } + if (port <= 0) { + throw new AuthenticationConfigException("Database port '%s' is not valid".formatted(port)); + } + if (StringUtils.isEmpty(dbName)) { + throw new AuthenticationConfigException("Database name is not specified"); + } + if (StringUtils.isEmpty(driver)) { + throw new AuthenticationConfigException("Database driver is not specified"); + } + DatabaseDriver databaseDriver = DatabaseDriver.fromValue(driver); + if (databaseDriver == null) { + throw new AuthenticationConfigException("Database driver '%s' is not supported".formatted(driver)); + } + this(databaseDriver, host, port, dbName); + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java index 200a62df..0783c20c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java @@ -1,5 +1,10 @@ package javasabr.mqtt.auth.api.database; +import java.util.Arrays; +import java.util.function.Function; +import javasabr.rlib.collections.dictionary.DictionaryCollectors; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -13,5 +18,12 @@ public enum DatabaseDriver { POSTGRESQL("postgresql"); + private static final RefToRefDictionary CACHE = Arrays.stream(values()) + .collect(DictionaryCollectors.toRefToRefDictionary(DatabaseDriver::value, Function.identity())); + String value; + + public static DatabaseDriver fromValue(String value) { + return StringUtils.isEmpty(value) ? null : CACHE.get(value); + } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index 0432e516..23e0693b 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -1,6 +1,20 @@ package javasabr.mqtt.auth.api.database; import java.time.Duration; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) {} - +public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) { + public DatabasePoolProperties(int maxIdleTimeSeconds, int initialSize, int maxSize) { + if (maxIdleTimeSeconds <= 0) { + throw new AuthenticationConfigException("Database pool max idle time '%s' is not valid".formatted( + maxIdleTimeSeconds)); + } + if (initialSize <= 0) { + throw new AuthenticationConfigException("Database pool initial size '%s' is not valid".formatted(initialSize)); + } + if (maxSize <= 0) { + throw new AuthenticationConfigException("Database pool max size '%s' is not valid".formatted(maxSize)); + } + this(Duration.ofSeconds(maxIdleTimeSeconds), initialSize, maxSize); + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java new file mode 100644 index 00000000..e5c4e381 --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java @@ -0,0 +1,18 @@ +package javasabr.mqtt.auth.api.database; + +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; + +public record DatabaseTimeouts(String lockTimeout, String statementTimeout) { + + public DatabaseTimeouts(int lockTimeoutSeconds, int statementTimeoutSeconds) { + if (lockTimeoutSeconds <= 0) { + throw new AuthenticationConfigException("Database lock timeout '%s' is not valid".formatted( + lockTimeoutSeconds)); + } + if (statementTimeoutSeconds <= 0) { + throw new AuthenticationConfigException("Database statement timeout '%s' is not valid".formatted( + statementTimeoutSeconds)); + } + this("%ss".formatted(lockTimeoutSeconds), "%ss".formatted(statementTimeoutSeconds)); + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java deleted file mode 100644 index c2c5dcba..00000000 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutsProperties.java +++ /dev/null @@ -1,4 +0,0 @@ -package javasabr.mqtt.auth.api.database; - -public record DatabaseTimeoutsProperties(String lockTimeout, String statementTimeout) {} - diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java new file mode 100644 index 00000000..8e9b6a8d --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.auth.api.file; + +import java.net.URI; +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; + +public record FileProperties(URI path) { + public FileProperties { + if (path == null) { + throw new AuthenticationConfigException("File path cannot be null"); + } + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java similarity index 93% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java index ad5ba146..a894f09a 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/InMemoryCredentialsSource.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java @@ -1,10 +1,12 @@ -package javasabr.mqtt.auth.api; +package javasabr.mqtt.auth.api.file; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Properties; +import javasabr.mqtt.auth.api.CredentialsSource; +import javasabr.mqtt.auth.api.MqttCredentials; import javasabr.rlib.collections.dictionary.DictionaryCollectors; import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index c77d0daf..c7264396 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -1,64 +1,54 @@ package javasabr.mqtt.auth.service.config; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; -import javasabr.mqtt.auth.api.database.DatabaseDriver; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeouts; import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.convert.ApplicationConversionService; -import org.springframework.boot.convert.DurationUnit; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; -import org.springframework.core.convert.ConversionService; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") public class DatabaseCredentialsSourceSpringConfig { - @Bean - public ConversionService conversionService() { - return new ApplicationConversionService(); - } - @Bean DatabasePoolProperties databasePoolPropertiesRecord( - @Value("${authentication.credentials-source.database.pool.max-idle-time}") @DurationUnit(ChronoUnit.MINUTES) Duration maxIdleTime, + @Value("${authentication.credentials-source.database.pool.max-idle-time-seconds}") int maxIdleTimeSeconds, @Value("${authentication.credentials-source.database.pool.initial-size}") int initialSize, - @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize){ - return new DatabasePoolProperties(maxIdleTime, initialSize, maxSize); + @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize) { + return new DatabasePoolProperties(maxIdleTimeSeconds, initialSize, maxSize); } @Bean DatabaseConnectionProperties databaseConnectionPropertiesRecord( - @Value("${authentication.credentials-source.database.driver}") DatabaseDriver driver, + @Value("${authentication.credentials-source.database.driver}") String driver, @Value("${authentication.credentials-source.database.host}") String host, @Value("${authentication.credentials-source.database.port}") int port, - @Value("${authentication.credentials-source.database.name}") String name){ + @Value("${authentication.credentials-source.database.name}") String name) { return new DatabaseConnectionProperties(driver, host, port, name); } @Bean - DatabaseTimeoutsProperties databaseTimeoutsPropertiesRecord( - @Value("${authentication.credentials-source.database.timeout.lock-timeout}") String lockTimeout, - @Value("${authentication.credentials-source.database.timeout.statement-timeout}") String statementTimeout){ - return new DatabaseTimeoutsProperties(lockTimeout, statementTimeout); + DatabaseTimeouts databaseTimeoutsPropertiesRecord( + @Value("${authentication.credentials-source.database.timeout.lock-timeout-seconds}") int lockTimeoutSeconds, + @Value("${authentication.credentials-source.database.timeout.statement-timeout-seconds}") + int statementTimeoutSeconds) { + return new DatabaseTimeouts(lockTimeoutSeconds, statementTimeoutSeconds); } @Bean @DependsOn("flyway") CredentialsSource dbCredentialsSource( DatabasePoolProperties databasePoolProperties, - DatabaseTimeoutsProperties databaseTimeoutsProperties, + DatabaseTimeouts databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties , DatabaseCredentials readerDatabaseCredentials) { return DatabaseCredentialsSourceFactories.databaseCredentialsSource() diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index 82033ba7..7215a3dc 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -1,20 +1,25 @@ package javasabr.mqtt.auth.service.config; -import javasabr.mqtt.auth.api.FileProperties; -import javasabr.mqtt.auth.service.config.property.SpringFileProperties; +import java.net.URI; +import javasabr.mqtt.auth.api.file.FileProperties; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") -@EnableConfigurationProperties(SpringFileProperties.class) public class FileCredentialsSourceSpringConfig { + @Bean + FileProperties fileCredentialsSourceProperties( + @Value("${authentication.credentials-source.file.path}") URI path) { + return new FileProperties(path); + } + @Bean(initMethod = "init") FileCredentialsSource fileCredentialsSource(FileProperties fileCredentialsSourceProperties) { return new FileCredentialsSource(fileCredentialsSourceProperties.path()); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java deleted file mode 100644 index 40b4f6c6..00000000 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/property/SpringFileProperties.java +++ /dev/null @@ -1,8 +0,0 @@ -package javasabr.mqtt.auth.service.config.property; - -import java.net.URI; -import javasabr.mqtt.auth.api.FileProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "authentication.credentials-source.file") -public record SpringFileProperties(boolean enabled, URI path) implements FileProperties {} diff --git a/authentication-service/src/test/resources/application-test.properties b/authentication-service/src/test/resources/application-test.properties index 74daa8c8..93c04797 100644 --- a/authentication-service/src/test/resources/application-test.properties +++ b/authentication-service/src/test/resources/application-test.properties @@ -3,8 +3,8 @@ authentication.credentials-source.database.driver=postgresql authentication.credentials-source.database.host=localhost authentication.credentials-source.database.port=5432 authentication.credentials-source.database.name=testdb -authentication.credentials-source.database.pool.max-idle-time=30m +authentication.credentials-source.database.pool.max-idle-time-seconds=1800 authentication.credentials-source.database.pool.initial-size=5 authentication.credentials-source.database.pool.max-size=10 -authentication.credentials-source.database.timeout.lock-timeout=10s -authentication.credentials-source.database.timeout.statement-timeout=5min +authentication.credentials-source.database.timeout.lock-timeout-seconds=10 +authentication.credentials-source.database.timeout.statement-timeout-seconds=300 diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java index c486cbb1..645c145b 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java @@ -17,7 +17,7 @@ import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import javasabr.mqtt.auth.api.database.DatabaseTimeoutsProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeouts; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import lombok.Builder; import org.flywaydb.core.Flyway; @@ -41,7 +41,7 @@ private static Flyway createFlyway( @Builder(builderMethodName = "databaseCredentialsSource") private static DatabaseCredentialsSource createDatabaseCredentialsSource( DatabasePoolProperties databasePoolProperties, - DatabaseTimeoutsProperties databaseTimeoutsProperties, + DatabaseTimeouts databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 438fd58b..ed1c96be 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -5,7 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.api.InMemoryCredentialsSource; +import javasabr.mqtt.auth.api.file.InMemoryCredentialsSource; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; From ab3c8658aeaba44e2cc9c24ebea0c20a4e3f0a73 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:46:11 +0100 Subject: [PATCH 086/107] [broker-141] Improve property validation --- .../DatabaseConnectionProperties.java | 24 +++++++++---------- .../api/database/DatabasePoolProperties.java | 20 ++++++++-------- .../auth/api/database/DatabaseTimeouts.java | 13 ++++------ ...DatabaseCredentialsSourceSpringConfig.java | 6 ++--- ...=> DatabaseCredentialsSourceBuilders.java} | 3 ++- 5 files changed, 31 insertions(+), 35 deletions(-) rename credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/{DatabaseCredentialsSourceFactories.java => DatabaseCredentialsSourceBuilders.java} (97%) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 0a1fd0f4..d5f6afa5 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,27 +1,27 @@ package javasabr.mqtt.auth.api.database; +import static javasabr.mqtt.auth.api.database.DatabasePoolProperties.assertPositive; + import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.rlib.common.util.StringUtils; public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) { public DatabaseConnectionProperties(String driver, String host, int port, String dbName) { - if (StringUtils.isEmpty(host)) { - throw new AuthenticationConfigException("Database host is not specified"); - } - if (port <= 0) { - throw new AuthenticationConfigException("Database port '%s' is not valid".formatted(port)); - } - if (StringUtils.isEmpty(dbName)) { - throw new AuthenticationConfigException("Database name is not specified"); - } - if (StringUtils.isEmpty(driver)) { - throw new AuthenticationConfigException("Database driver is not specified"); - } + assertSpecified(host, "Database host is not specified"); + assertPositive(port, "Database port '%s' is not valid"); + assertSpecified(dbName, "Database name is not specified"); + assertSpecified(driver, "Database driver is not specified"); DatabaseDriver databaseDriver = DatabaseDriver.fromValue(driver); if (databaseDriver == null) { throw new AuthenticationConfigException("Database driver '%s' is not supported".formatted(driver)); } this(databaseDriver, host, port, dbName); } + + public static void assertSpecified(String value, String message) { + if (StringUtils.isEmpty(value)) { + throw new AuthenticationConfigException(message); + } + } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index 23e0693b..49672538 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -5,16 +5,16 @@ public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) { public DatabasePoolProperties(int maxIdleTimeSeconds, int initialSize, int maxSize) { - if (maxIdleTimeSeconds <= 0) { - throw new AuthenticationConfigException("Database pool max idle time '%s' is not valid".formatted( - maxIdleTimeSeconds)); - } - if (initialSize <= 0) { - throw new AuthenticationConfigException("Database pool initial size '%s' is not valid".formatted(initialSize)); - } - if (maxSize <= 0) { - throw new AuthenticationConfigException("Database pool max size '%s' is not valid".formatted(maxSize)); - } + assertPositive(maxIdleTimeSeconds, "Database pool max idle time '%s' is not valid"); + assertPositive(initialSize, "Database pool initial size '%s' is not valid"); + assertPositive(maxSize, "Database pool max size '%s' is not valid"); + this(Duration.ofSeconds(maxIdleTimeSeconds), initialSize, maxSize); } + + public static void assertPositive(int value, String message) { + if (value <= 0) { + throw new AuthenticationConfigException(message.formatted(value)); + } + } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java index e5c4e381..f90fc494 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java @@ -1,18 +1,13 @@ package javasabr.mqtt.auth.api.database; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; +import static javasabr.mqtt.auth.api.database.DatabasePoolProperties.assertPositive; public record DatabaseTimeouts(String lockTimeout, String statementTimeout) { public DatabaseTimeouts(int lockTimeoutSeconds, int statementTimeoutSeconds) { - if (lockTimeoutSeconds <= 0) { - throw new AuthenticationConfigException("Database lock timeout '%s' is not valid".formatted( - lockTimeoutSeconds)); - } - if (statementTimeoutSeconds <= 0) { - throw new AuthenticationConfigException("Database statement timeout '%s' is not valid".formatted( - statementTimeoutSeconds)); - } + assertPositive(lockTimeoutSeconds, "Database lock timeout '%s' is not valid"); + assertPositive(statementTimeoutSeconds, "Database statement timeout '%s' is not valid"); + this("%ss".formatted(lockTimeoutSeconds), "%ss".formatted(statementTimeoutSeconds)); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index c7264396..d31b4cb6 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -5,7 +5,7 @@ import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; import javasabr.mqtt.auth.api.database.DatabaseTimeouts; -import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceFactories; +import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceBuilders; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -51,7 +51,7 @@ CredentialsSource dbCredentialsSource( DatabaseTimeouts databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties , DatabaseCredentials readerDatabaseCredentials) { - return DatabaseCredentialsSourceFactories.databaseCredentialsSource() + return DatabaseCredentialsSourceBuilders.databaseCredentialsSource() .databasePoolProperties(databasePoolProperties) .databaseTimeoutsProperties(databaseTimeoutsProperties) .databaseConnectionProperties(databaseConnectionProperties) @@ -63,7 +63,7 @@ CredentialsSource dbCredentialsSource( Flyway flyway( DatabaseConnectionProperties databaseCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { - return DatabaseCredentialsSourceFactories.flyway() + return DatabaseCredentialsSourceBuilders.flyway() .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) .adminDatabaseCredentials(adminDatabaseCredentials) .build(); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java similarity index 97% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java index 645c145b..3b94afd0 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceFactories.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java @@ -22,7 +22,8 @@ import lombok.Builder; import org.flywaydb.core.Flyway; -public class DatabaseCredentialsSourceFactories { +@SuppressWarnings("unused") +public class DatabaseCredentialsSourceBuilders { @Builder(builderMethodName = "flyway") private static Flyway createFlyway( From 76f7af424eeabdf57c2c002063802877e6aced88 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:55:18 +0100 Subject: [PATCH 087/107] [broker-141] Rename Spring beans related to Credentials Source --- .../DatabaseCredentialsSourceSpringConfig.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index d31b4cb6..a676a96f 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -20,7 +20,7 @@ public class DatabaseCredentialsSourceSpringConfig { @Bean - DatabasePoolProperties databasePoolPropertiesRecord( + DatabasePoolProperties credentialsSourceDatabasePoolProperties( @Value("${authentication.credentials-source.database.pool.max-idle-time-seconds}") int maxIdleTimeSeconds, @Value("${authentication.credentials-source.database.pool.initial-size}") int initialSize, @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize) { @@ -28,7 +28,7 @@ DatabasePoolProperties databasePoolPropertiesRecord( } @Bean - DatabaseConnectionProperties databaseConnectionPropertiesRecord( + DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties( @Value("${authentication.credentials-source.database.driver}") String driver, @Value("${authentication.credentials-source.database.host}") String host, @Value("${authentication.credentials-source.database.port}") int port, @@ -37,7 +37,7 @@ DatabaseConnectionProperties databaseConnectionPropertiesRecord( } @Bean - DatabaseTimeouts databaseTimeoutsPropertiesRecord( + DatabaseTimeouts credentialsSourceDatabaseTimeoutsProperties( @Value("${authentication.credentials-source.database.timeout.lock-timeout-seconds}") int lockTimeoutSeconds, @Value("${authentication.credentials-source.database.timeout.statement-timeout-seconds}") int statementTimeoutSeconds) { @@ -45,11 +45,11 @@ DatabaseTimeouts databaseTimeoutsPropertiesRecord( } @Bean - @DependsOn("flyway") - CredentialsSource dbCredentialsSource( + @DependsOn("credentialsSourceFlyway") + CredentialsSource databaseCredentialsSource( DatabasePoolProperties databasePoolProperties, DatabaseTimeouts databaseTimeoutsProperties, - DatabaseConnectionProperties databaseConnectionProperties , + DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { return DatabaseCredentialsSourceBuilders.databaseCredentialsSource() .databasePoolProperties(databasePoolProperties) @@ -60,7 +60,7 @@ CredentialsSource dbCredentialsSource( } @Bean(initMethod = "migrate") - Flyway flyway( + Flyway credentialsSourceFlyway( DatabaseConnectionProperties databaseCredentialsSourceProperties, DatabaseCredentials adminDatabaseCredentials) { return DatabaseCredentialsSourceBuilders.flyway() From fc269eb4e48bdc1a89100b0179b928fbd6232978 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:57:29 +0100 Subject: [PATCH 088/107] [broker-141] Revert unnecessary changes --- .../mqtt/broker/application/config/MqttBrokerSpringConfig.java | 2 +- .../java/javasabr/mqtt/service/impl/DefaultTopicService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 24d2fe16..9eac4af9 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -361,7 +361,7 @@ ServerNetworkConfig externalNetworkConfig( @Value("${mqtt.external.network.read.buffer.size:512}") int readBufferSize, @Value("${mqtt.external.network.pending.buffer.size:1024}") int pendingBufferSize, @Value("${mqtt.external.network.write.buffer.size:512}") int writeBufferSize, - @Value("${mqtt.external.network.thread.group.dbName:ExternalNetwork}") String threadGroupName, + @Value("${mqtt.external.network.thread.group.name:ExternalNetwork}") String threadGroupName, @Value("${mqtt.external.network.thread.count:1}") int threadGroupMaxSize) { return ServerNetworkConfig.SimpleServerNetworkConfig .builder() diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java index 68b31221..c4981b89 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java @@ -39,7 +39,7 @@ public boolean isValidTopicFilter(NetworkMqttUser user, String rawTopicFilter) { @Override public TopicName createTopicName(NetworkMqttUser user, String rawTopicName) { if (!TopicValidator.validateTopicName(rawTopicName)) { - log.warning(user.clientId(), rawTopicName, "[%s] Invalid topic dbName:[%s]"::formatted); + log.warning(user.clientId(), rawTopicName, "[%s] Invalid topic name:[%s]"::formatted); return TopicName.INVALID_TOPIC_NAME; } return TopicName.valueOf(rawTopicName); From 8bb6c58997ca6b5d573a774c40720f3e0575330c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:06:46 +0100 Subject: [PATCH 089/107] [broker-141] Introduce PropertyAssert --- .../DatabaseConnectionProperties.java | 23 ++++------------- .../api/database/DatabasePoolProperties.java | 14 +++-------- .../auth/api/database/DatabaseTimeouts.java | 6 ++--- .../auth/api/database/PropertyAssert.java | 25 +++++++++++++++++++ 4 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index d5f6afa5..5a501323 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,27 +1,14 @@ package javasabr.mqtt.auth.api.database; -import static javasabr.mqtt.auth.api.database.DatabasePoolProperties.assertPositive; - -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -import javasabr.rlib.common.util.StringUtils; - public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) { public DatabaseConnectionProperties(String driver, String host, int port, String dbName) { - assertSpecified(host, "Database host is not specified"); - assertPositive(port, "Database port '%s' is not valid"); - assertSpecified(dbName, "Database name is not specified"); - assertSpecified(driver, "Database driver is not specified"); DatabaseDriver databaseDriver = DatabaseDriver.fromValue(driver); - if (databaseDriver == null) { - throw new AuthenticationConfigException("Database driver '%s' is not supported".formatted(driver)); - } + PropertyAssert.notNull(databaseDriver, "Database driver '%s' is not supported".formatted(driver)); + PropertyAssert.notEmpty(host, "Database host is not specified"); + PropertyAssert.positive(port, "Database port '%s' is not valid"); + PropertyAssert.notEmpty(dbName, "Database name is not specified"); + PropertyAssert.notEmpty(driver, "Database driver is not specified"); this(databaseDriver, host, port, dbName); } - - public static void assertSpecified(String value, String message) { - if (StringUtils.isEmpty(value)) { - throw new AuthenticationConfigException(message); - } - } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index 49672538..cdc684e0 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -1,20 +1,14 @@ package javasabr.mqtt.auth.api.database; import java.time.Duration; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) { + public DatabasePoolProperties(int maxIdleTimeSeconds, int initialSize, int maxSize) { - assertPositive(maxIdleTimeSeconds, "Database pool max idle time '%s' is not valid"); - assertPositive(initialSize, "Database pool initial size '%s' is not valid"); - assertPositive(maxSize, "Database pool max size '%s' is not valid"); + PropertyAssert.positive(maxIdleTimeSeconds, "Database pool max idle time '%s' is not valid"); + PropertyAssert.positive(initialSize, "Database pool initial size '%s' is not valid"); + PropertyAssert.positive(maxSize, "Database pool max size '%s' is not valid"); this(Duration.ofSeconds(maxIdleTimeSeconds), initialSize, maxSize); } - - public static void assertPositive(int value, String message) { - if (value <= 0) { - throw new AuthenticationConfigException(message.formatted(value)); - } - } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java index f90fc494..4b798b91 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java @@ -1,12 +1,10 @@ package javasabr.mqtt.auth.api.database; -import static javasabr.mqtt.auth.api.database.DatabasePoolProperties.assertPositive; - public record DatabaseTimeouts(String lockTimeout, String statementTimeout) { public DatabaseTimeouts(int lockTimeoutSeconds, int statementTimeoutSeconds) { - assertPositive(lockTimeoutSeconds, "Database lock timeout '%s' is not valid"); - assertPositive(statementTimeoutSeconds, "Database statement timeout '%s' is not valid"); + PropertyAssert.positive(lockTimeoutSeconds, "Database lock timeout '%s' is not valid"); + PropertyAssert.positive(statementTimeoutSeconds, "Database statement timeout '%s' is not valid"); this("%ss".formatted(lockTimeoutSeconds), "%ss".formatted(statementTimeoutSeconds)); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java new file mode 100644 index 00000000..5349e287 --- /dev/null +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java @@ -0,0 +1,25 @@ +package javasabr.mqtt.auth.api.database; + +import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; +import javasabr.rlib.common.util.StringUtils; + +public class PropertyAssert { + + public static void positive(int value, String message) { + if (value <= 0) { + throw new AuthenticationConfigException(message.formatted(value)); + } + } + + public static void notEmpty(String value, String message) { + if (StringUtils.isEmpty(value)) { + throw new AuthenticationConfigException(message); + } + } + + public static void notNull(Object value, String message) { + if (value == null) { + throw new AuthenticationConfigException(message); + } + } +} From b7526ba74afba6d60dfa5a0c450873d2094b2280 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:23:57 +0100 Subject: [PATCH 090/107] [broker-141] Move InMemoryCredentialsSource to credentials-source-file --- .../broker/application/config/MqttBrokerSpringConfig.java | 4 ++-- .../mqtt/auth/credentials/source/FileCredentialsSource.java | 1 - .../auth/credentials/source}/InMemoryCredentialsSource.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) rename {authentication-api/src/main/java/javasabr/mqtt/auth/api/file => credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source}/InMemoryCredentialsSource.java (97%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 9eac4af9..fb3425ab 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -73,8 +73,8 @@ import org.springframework.core.env.Environment; @Import({ - GroovyDslBasedAclServiceSpringConfig.class, - AuthenticationServiceSpringConfig.class + AuthenticationServiceSpringConfig.class, + GroovyDslBasedAclServiceSpringConfig.class }) @CustomLog @Configuration(proxyBeanMethods = false) diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index ed1c96be..22b7348e 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -5,7 +5,6 @@ import java.nio.file.Files; import java.nio.file.Path; import javasabr.mqtt.auth.api.CredentialsSourceType; -import javasabr.mqtt.auth.api.file.InMemoryCredentialsSource; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/InMemoryCredentialsSource.java similarity index 97% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java rename to credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/InMemoryCredentialsSource.java index a894f09a..aecc7710 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/InMemoryCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/InMemoryCredentialsSource.java @@ -1,4 +1,4 @@ -package javasabr.mqtt.auth.api.file; +package javasabr.mqtt.auth.credentials.source; import java.io.IOException; import java.io.InputStream; From df14a9845284bfab00887434bedf170d6a2ce51c Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:18:56 +0100 Subject: [PATCH 091/107] [broker-141] Optimize DatabaseCredentialsSource query --- .../credentials/source/DatabaseCredentialsSource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index feea126c..8364b1c0 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -3,7 +3,6 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.Result; -import java.util.Objects; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.MqttCredentials; @@ -19,14 +18,15 @@ public class DatabaseCredentialsSource implements CredentialsSource { @SuppressWarnings("SqlNoDataSourceInspection") private static final String CREDENTIALS_QUERY = """ - SELECT COUNT(*) > 0 + SELECT 1 FROM user_credentials WHERE username = $1 - AND password = $2; + AND password = $2 + LIMIT 1 """; private static Mono isCredentialsRecordFound(Result result) { - return Mono.from(result.map((row, _) -> Objects.equals(row.get(0, Boolean.class), Boolean.TRUE))); + return Mono.from(result.map((_, _) -> true)).defaultIfEmpty(false); } ConnectionFactory connectionFactory; From f9ac55df766a1b6da6880676696a3e9eed4ab772 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:30:03 +0100 Subject: [PATCH 092/107] [broker-141] Move PropertyAssert to base module --- .../auth/api/database/DatabaseConnectionProperties.java | 2 ++ .../mqtt/auth/api/database/DatabasePoolProperties.java | 1 + .../mqtt/auth/api/database/DatabaseTimeouts.java | 2 ++ .../mqtt/base/util/BrokerConfigurationException.java | 8 ++++++++ .../java/javasabr/mqtt/base/util}/PropertyAssert.java | 9 ++++----- 5 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 base/src/main/java/javasabr/mqtt/base/util/BrokerConfigurationException.java rename {authentication-api/src/main/java/javasabr/mqtt/auth/api/database => base/src/main/java/javasabr/mqtt/base/util}/PropertyAssert.java (56%) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 5a501323..9ce11981 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -1,5 +1,7 @@ package javasabr.mqtt.auth.api.database; +import javasabr.mqtt.base.util.PropertyAssert; + public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) { public DatabaseConnectionProperties(String driver, String host, int port, String dbName) { diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index cdc684e0..19ab236f 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -1,6 +1,7 @@ package javasabr.mqtt.auth.api.database; import java.time.Duration; +import javasabr.mqtt.base.util.PropertyAssert; public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) { diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java index 4b798b91..fdfc6320 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java @@ -1,5 +1,7 @@ package javasabr.mqtt.auth.api.database; +import javasabr.mqtt.base.util.PropertyAssert; + public record DatabaseTimeouts(String lockTimeout, String statementTimeout) { public DatabaseTimeouts(int lockTimeoutSeconds, int statementTimeoutSeconds) { diff --git a/base/src/main/java/javasabr/mqtt/base/util/BrokerConfigurationException.java b/base/src/main/java/javasabr/mqtt/base/util/BrokerConfigurationException.java new file mode 100644 index 00000000..188f002a --- /dev/null +++ b/base/src/main/java/javasabr/mqtt/base/util/BrokerConfigurationException.java @@ -0,0 +1,8 @@ +package javasabr.mqtt.base.util; + +public class BrokerConfigurationException extends RuntimeException { + + public BrokerConfigurationException(String message) { + super(message); + } +} diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java b/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java similarity index 56% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java rename to base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java index 5349e287..6997370c 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/PropertyAssert.java +++ b/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java @@ -1,25 +1,24 @@ -package javasabr.mqtt.auth.api.database; +package javasabr.mqtt.base.util; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; import javasabr.rlib.common.util.StringUtils; public class PropertyAssert { public static void positive(int value, String message) { if (value <= 0) { - throw new AuthenticationConfigException(message.formatted(value)); + throw new BrokerConfigurationException(message.formatted(value)); } } public static void notEmpty(String value, String message) { if (StringUtils.isEmpty(value)) { - throw new AuthenticationConfigException(message); + throw new BrokerConfigurationException(message); } } public static void notNull(Object value, String message) { if (value == null) { - throw new AuthenticationConfigException(message); + throw new BrokerConfigurationException(message); } } } From 6cb6f308010ebc8cc5d5478aaa9a3666f13a6720 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:31:07 +0100 Subject: [PATCH 093/107] [broker-141] Rename databaseCredentialsSourceProperties autowired candidate --- .../java/javasabr/mqtt/auth/api/MqttCredentials.java | 3 ++- .../config/DatabaseCredentialsSourceSpringConfig.java | 4 ++-- .../config/DatabaseCredentialsSourceBuilders.java | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index ee5d1581..baeff8e1 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -2,11 +2,12 @@ import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.StringUtils; +import org.jspecify.annotations.Nullable; public record MqttCredentials( String username, byte[] password, - AuthenticationMethod authenticationMethod, + @Nullable AuthenticationMethod authenticationMethod, byte[] authenticationData) { public boolean isAnonymous() { diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index a676a96f..87c086c5 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -61,10 +61,10 @@ CredentialsSource databaseCredentialsSource( @Bean(initMethod = "migrate") Flyway credentialsSourceFlyway( - DatabaseConnectionProperties databaseCredentialsSourceProperties, + DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { return DatabaseCredentialsSourceBuilders.flyway() - .databaseCredentialsSourceProperties(databaseCredentialsSourceProperties) + .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) .build(); } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java index 3b94afd0..a0fe1ea1 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java @@ -27,13 +27,13 @@ public class DatabaseCredentialsSourceBuilders { @Builder(builderMethodName = "flyway") private static Flyway createFlyway( - DatabaseConnectionProperties databaseCredentialsSourceProperties, + DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - databaseCredentialsSourceProperties.driver().value(), - databaseCredentialsSourceProperties.host(), - databaseCredentialsSourceProperties.port(), - databaseCredentialsSourceProperties.dbName()); + databaseConnectionProperties.driver().value(), + databaseConnectionProperties.host(), + databaseConnectionProperties.port(), + databaseConnectionProperties.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) .load(); From c05fea47b1a4fb0daa4188bf97839ddcf0fbe309 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:31:47 +0100 Subject: [PATCH 094/107] [broker-141] Rename DatabaseCredentialsSourceBuilder --- .../config/DatabaseCredentialsSourceSpringConfig.java | 6 +++--- ...eBuilders.java => DatabaseCredentialsSourceBuilder.java} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/{DatabaseCredentialsSourceBuilders.java => DatabaseCredentialsSourceBuilder.java} (98%) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 87c086c5..d70712f0 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -5,7 +5,7 @@ import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; import javasabr.mqtt.auth.api.database.DatabaseTimeouts; -import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceBuilders; +import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceBuilder; import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -51,7 +51,7 @@ CredentialsSource databaseCredentialsSource( DatabaseTimeouts databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { - return DatabaseCredentialsSourceBuilders.databaseCredentialsSource() + return DatabaseCredentialsSourceBuilder.databaseCredentialsSource() .databasePoolProperties(databasePoolProperties) .databaseTimeoutsProperties(databaseTimeoutsProperties) .databaseConnectionProperties(databaseConnectionProperties) @@ -63,7 +63,7 @@ CredentialsSource databaseCredentialsSource( Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { - return DatabaseCredentialsSourceBuilders.flyway() + return DatabaseCredentialsSourceBuilder.flyway() .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) .build(); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java similarity index 98% rename from credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java rename to credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java index a0fe1ea1..cb35ed72 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilders.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java @@ -23,7 +23,7 @@ import org.flywaydb.core.Flyway; @SuppressWarnings("unused") -public class DatabaseCredentialsSourceBuilders { +public class DatabaseCredentialsSourceBuilder { @Builder(builderMethodName = "flyway") private static Flyway createFlyway( From 1b3875db43df8e3d8ba09eb2216680f1e550fe27 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:56:07 +0100 Subject: [PATCH 095/107] [broker-141] Separate Flyway and DatabaseCredentialsSource builders --- ...DatabaseCredentialsSourceSpringConfig.java | 10 +-- .../source/DatabaseCredentialsSource.java | 54 +++++++++++++-- .../credentials/source/FlywayBuilder.java | 24 +++++++ .../DatabaseCredentialsSourceBuilder.java | 68 ------------------- 4 files changed, 77 insertions(+), 79 deletions(-) create mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java delete mode 100644 credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index d70712f0..95c5f6b7 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -5,8 +5,8 @@ import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; import javasabr.mqtt.auth.api.database.DatabaseTimeouts; -import javasabr.mqtt.auth.credentials.source.config.DatabaseCredentialsSourceBuilder; -import org.flywaydb.core.Flyway; +import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; +import javasabr.mqtt.auth.credentials.source.FlywayBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -51,7 +51,7 @@ CredentialsSource databaseCredentialsSource( DatabaseTimeouts databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { - return DatabaseCredentialsSourceBuilder.databaseCredentialsSource() + return DatabaseCredentialsSource.builder() .databasePoolProperties(databasePoolProperties) .databaseTimeoutsProperties(databaseTimeoutsProperties) .databaseConnectionProperties(databaseConnectionProperties) @@ -60,10 +60,10 @@ CredentialsSource databaseCredentialsSource( } @Bean(initMethod = "migrate") - Flyway credentialsSourceFlyway( + org.flywaydb.core.Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { - return DatabaseCredentialsSourceBuilder.flyway() + return FlywayBuilder.create() .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) .build(); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 8364b1c0..751b5083 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -1,18 +1,34 @@ package javasabr.mqtt.auth.credentials.source; +import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; +import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; +import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; +import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; +import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; +import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; +import static io.r2dbc.spi.ConnectionFactoryOptions.USER; + +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.Result; +import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.MqttCredentials; +import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.database.DatabaseCredentials; +import javasabr.mqtt.auth.api.database.DatabasePoolProperties; +import javasabr.mqtt.auth.api.database.DatabaseTimeouts; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; +import lombok.Builder; import lombok.experimental.FieldDefaults; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DatabaseCredentialsSource implements CredentialsSource { @@ -25,12 +41,34 @@ public class DatabaseCredentialsSource implements CredentialsSource { LIMIT 1 """; - private static Mono isCredentialsRecordFound(Result result) { - return Mono.from(result.map((_, _) -> true)).defaultIfEmpty(false); - } - ConnectionFactory connectionFactory; + @Builder + public DatabaseCredentialsSource( + DatabasePoolProperties databasePoolProperties, + DatabaseTimeouts databaseTimeoutsProperties, + DatabaseConnectionProperties databaseConnectionProperties, + DatabaseCredentials readerDatabaseCredentials) { + Map timeoutOptions = Map.of( + "lock_timeout", databaseTimeoutsProperties.lockTimeout(), + "statement_timeout", databaseTimeoutsProperties.statementTimeout()); + ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() + .option(DATABASE, databaseConnectionProperties.dbName()) + .option(DRIVER, databaseConnectionProperties.driver().value()) + .option(HOST, databaseConnectionProperties.host()) + .option(PORT, databaseConnectionProperties.port()) + .option(USER, readerDatabaseCredentials.username()) + .option(PASSWORD, readerDatabaseCredentials.password()) + .option(OPTIONS, timeoutOptions).build(); + ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) + .maxIdleTime(databasePoolProperties.maxIdleTime()) + .maxSize(databasePoolProperties.maxSize()) + .initialSize(databasePoolProperties.initialSize()) + .build(); + this.connectionFactory = new ConnectionPool(configuration); + } + @Override public CredentialsSourceType getType() { return CredentialsSourceType.DATABASE; @@ -59,4 +97,8 @@ private Mono executeCredentialsQuery(Connection connection, MqttCredent .flatMap(DatabaseCredentialsSource::isCredentialsRecordFound) .defaultIfEmpty(false); } + + private static Mono isCredentialsRecordFound(Result result) { + return Mono.from(result.map((_, _) -> true)).defaultIfEmpty(false); + } } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java new file mode 100644 index 00000000..bf3ac783 --- /dev/null +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java @@ -0,0 +1,24 @@ +package javasabr.mqtt.auth.credentials.source; + +import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; +import javasabr.mqtt.auth.api.database.DatabaseCredentials; +import lombok.Builder; +import org.flywaydb.core.Flyway; + +public class FlywayBuilder { + + @SuppressWarnings("unused") + @Builder(builderMethodName = "create", builderClassName = "FlywaySupport") + private static Flyway createFlyway( + DatabaseConnectionProperties databaseConnectionProperties, + DatabaseCredentials adminDatabaseCredentials) { + String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( + databaseConnectionProperties.driver().value(), + databaseConnectionProperties.host(), + databaseConnectionProperties.port(), + databaseConnectionProperties.dbName()); + return Flyway.configure() + .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) + .load(); + } +} diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java deleted file mode 100644 index cb35ed72..00000000 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/config/DatabaseCredentialsSourceBuilder.java +++ /dev/null @@ -1,68 +0,0 @@ -package javasabr.mqtt.auth.credentials.source.config; - -import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS; -import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; -import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; -import static io.r2dbc.spi.ConnectionFactoryOptions.HOST; -import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; -import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; -import static io.r2dbc.spi.ConnectionFactoryOptions.USER; - -import io.r2dbc.pool.ConnectionPool; -import io.r2dbc.pool.ConnectionPoolConfiguration; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.ConnectionFactoryOptions; -import java.util.Map; -import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; -import javasabr.mqtt.auth.api.database.DatabaseCredentials; -import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import javasabr.mqtt.auth.api.database.DatabaseTimeouts; -import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; -import lombok.Builder; -import org.flywaydb.core.Flyway; - -@SuppressWarnings("unused") -public class DatabaseCredentialsSourceBuilder { - - @Builder(builderMethodName = "flyway") - private static Flyway createFlyway( - DatabaseConnectionProperties databaseConnectionProperties, - DatabaseCredentials adminDatabaseCredentials) { - String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( - databaseConnectionProperties.driver().value(), - databaseConnectionProperties.host(), - databaseConnectionProperties.port(), - databaseConnectionProperties.dbName()); - return Flyway.configure() - .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) - .load(); - } - - @Builder(builderMethodName = "databaseCredentialsSource") - private static DatabaseCredentialsSource createDatabaseCredentialsSource( - DatabasePoolProperties databasePoolProperties, - DatabaseTimeouts databaseTimeoutsProperties, - DatabaseConnectionProperties databaseConnectionProperties, - DatabaseCredentials readerDatabaseCredentials) { - Map timeoutOptions = Map.of( - "lock_timeout", databaseTimeoutsProperties.lockTimeout(), - "statement_timeout", databaseTimeoutsProperties.statementTimeout()); - ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder() - .option(DATABASE, databaseConnectionProperties.dbName()) - .option(DRIVER, databaseConnectionProperties.driver().value()) - .option(HOST, databaseConnectionProperties.host()) - .option(PORT, databaseConnectionProperties.port()) - .option(USER, readerDatabaseCredentials.username()) - .option(PASSWORD, readerDatabaseCredentials.password()) - .option(OPTIONS, timeoutOptions).build(); - ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); - ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) - .maxIdleTime(databasePoolProperties.maxIdleTime()) - .maxSize(databasePoolProperties.maxSize()) - .initialSize(databasePoolProperties.initialSize()) - .build(); - ConnectionPool connectionPool = new ConnectionPool(configuration); - return new DatabaseCredentialsSource(connectionPool); - } -} From 3bc1bdd10ba977c1224abfb25ab9e010b59f2d61 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:09:21 +0100 Subject: [PATCH 096/107] [broker-141] Remove unnecessary log4j2.xml --- .../src/test/resources/log4j2.xml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 authentication-service/src/test/resources/log4j2.xml diff --git a/authentication-service/src/test/resources/log4j2.xml b/authentication-service/src/test/resources/log4j2.xml deleted file mode 100644 index ceb3b07e..00000000 --- a/authentication-service/src/test/resources/log4j2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - From c169ca9f5e570c94b98fec1e604b052def29e428 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:28:31 +0100 Subject: [PATCH 097/107] [broker-141] Move PostgreSQLContainer to Spring cpnfig --- .../service/AuthenticationServiceTest.groovy | 21 ------------------- .../service/DatabaseTestSpringConfig.java | 20 ++++++++++++++++++ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 27f88265..6fec1f5e 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -1,18 +1,12 @@ package javasabr.mqtt.broker.application.service - import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.AuthenticationService import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.api.CredentialsSourceType import javasabr.mqtt.auth.api.MqttCredentials import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource import org.springframework.test.context.TestPropertySource -import org.testcontainers.postgresql.PostgreSQLContainer -import org.testcontainers.spock.Testcontainers -import spock.lang.Shared import java.nio.charset.StandardCharsets @@ -23,7 +17,6 @@ import java.nio.charset.StandardCharsets "authentication.credentials-source.file.enabled=true", "authentication.credentials-source.database.enabled=true" ]) -@Testcontainers class AuthenticationServiceTest extends IntegrationSpecification { @Autowired @@ -31,20 +24,6 @@ class AuthenticationServiceTest extends IntegrationSpecification { @Autowired List authenticationProviders - @Shared - static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.12") - .withDatabaseName("testdb") - .withUsername("user") - .withPassword("") - - @DynamicPropertySource - static void configureProperties(DynamicPropertyRegistry registry) { - postgreSQLContainer.start() - registry.add( - "authentication.credentials-source.database.port", - { "${postgreSQLContainer.getMappedPort(5432)}" }) - } - @Autowired AuthenticationService authenticationService diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index 3eb8d675..1667b439 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -3,17 +3,37 @@ import javasabr.mqtt.auth.api.database.DatabaseCredentials; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.test.context.DynamicPropertyRegistrar; +import org.testcontainers.postgresql.PostgreSQLContainer; @Configuration public class DatabaseTestSpringConfig { @Bean + @DependsOn("postgreSQLContainer") public DatabaseCredentials readerDatabaseCredentials() { return new DatabaseCredentials("user", ""); } @Bean + @DependsOn("postgreSQLContainer") public DatabaseCredentials adminDatabaseCredentials() { return new DatabaseCredentials("user", ""); } + + @Bean(initMethod = "start", destroyMethod = "stop") + public PostgreSQLContainer postgreSQLContainer() { + return new PostgreSQLContainer("postgres:9.6.12") + .withDatabaseName("testdb") + .withUsername("user") + .withPassword(""); + } + + @Bean + public DynamicPropertyRegistrar configurePostgresSQLProperties(PostgreSQLContainer container) { + return registry -> registry.add( + "authentication.credentials-source.database.port", + () -> container.getMappedPort(5432)); + } } From 09568c2c9eb145016db983d04678dc7fdb76eacb Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:02:04 +0100 Subject: [PATCH 098/107] [broker-141] Improve logging --- .../AclEngineBasedAuthorizationService.java | 2 +- .../mqtt/auth/api/AuthenticationMethod.java | 3 +- .../mqtt/auth/api/MqttCredentials.java | 1 + .../DatabaseConnectionProperties.java | 11 ++++--- .../auth/api/database/DatabaseDriver.java | 3 +- .../api/database/DatabasePoolProperties.java | 6 ++-- ...ts.java => DatabaseTimeoutProperties.java} | 8 ++--- authentication-service/build.gradle | 2 -- .../service/DefaultAuthenticationService.java | 31 +++++++++++++------ .../AuthenticationServiceSpringConfig.java | 3 +- ...DatabaseCredentialsSourceSpringConfig.java | 15 ++++++--- .../FileCredentialsSourceSpringConfig.java | 4 +++ .../service/AuthenticationServiceTest.groovy | 3 +- .../service/DatabaseTestSpringConfig.java | 1 + .../mqtt/base/util/PropertyAssert.java | 12 +++---- .../impl/ConnectInMqttInMessageHandler.java | 2 ++ .../source/DatabaseCredentialsSource.java | 4 +-- .../credentials/source/FlywayBuilder.java | 2 +- 18 files changed, 71 insertions(+), 42 deletions(-) rename authentication-api/src/main/java/javasabr/mqtt/auth/api/database/{DatabaseTimeouts.java => DatabaseTimeoutProperties.java} (58%) diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java index fa2f2f83..27f6cd2b 100644 --- a/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/AclEngineBasedAuthorizationService.java @@ -26,7 +26,7 @@ public boolean authorizeSubscribe(MqttUser user, TopicFilter topicFilter) { } // we know that writing this to not volatile field will not apply it for all threads immediately, - // but for us it's not critical comparing to cost of reading volatile field + // but for us, it's not critical comparing to cost of reading volatile field protected synchronized void switchTo(AclEngine newEngine) { this.engine = newEngine; } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index b4c5ae54..20c1eb91 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @Getter @Accessors @@ -28,7 +29,7 @@ public enum AuthenticationMethod { String value; int priority; - public static AuthenticationMethod fromValue(String value) { + public static @Nullable AuthenticationMethod fromValue(String value) { return StringUtils.isEmpty(value) ? null : CACHE.get(value); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java index baeff8e1..0198eb3a 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/MqttCredentials.java @@ -5,6 +5,7 @@ import org.jspecify.annotations.Nullable; public record MqttCredentials( + String clientId, String username, byte[] password, @Nullable AuthenticationMethod authenticationMethod, diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 9ce11981..527fa0bc 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -5,12 +5,13 @@ public record DatabaseConnectionProperties(DatabaseDriver driver, String host, int port, String dbName) { public DatabaseConnectionProperties(String driver, String host, int port, String dbName) { + PropertyAssert.notEmpty(host, "Database host"); + PropertyAssert.positive(port, "Database port"); + PropertyAssert.notEmpty(dbName, "Database name"); + PropertyAssert.notEmpty(driver, "Database driver"); DatabaseDriver databaseDriver = DatabaseDriver.fromValue(driver); - PropertyAssert.notNull(databaseDriver, "Database driver '%s' is not supported".formatted(driver)); - PropertyAssert.notEmpty(host, "Database host is not specified"); - PropertyAssert.positive(port, "Database port '%s' is not valid"); - PropertyAssert.notEmpty(dbName, "Database name is not specified"); - PropertyAssert.notEmpty(driver, "Database driver is not specified"); + PropertyAssert.notNull(databaseDriver, "Database driver '%s' is not supported", driver); + this(databaseDriver, host, port, dbName); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java index 0783c20c..e6597516 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @Getter @Accessors @@ -23,7 +24,7 @@ public enum DatabaseDriver { String value; - public static DatabaseDriver fromValue(String value) { + public static @Nullable DatabaseDriver fromValue(String value) { return StringUtils.isEmpty(value) ? null : CACHE.get(value); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java index 19ab236f..5a1a0330 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabasePoolProperties.java @@ -6,9 +6,9 @@ public record DatabasePoolProperties(Duration maxIdleTime, int initialSize, int maxSize) { public DatabasePoolProperties(int maxIdleTimeSeconds, int initialSize, int maxSize) { - PropertyAssert.positive(maxIdleTimeSeconds, "Database pool max idle time '%s' is not valid"); - PropertyAssert.positive(initialSize, "Database pool initial size '%s' is not valid"); - PropertyAssert.positive(maxSize, "Database pool max size '%s' is not valid"); + PropertyAssert.positive(maxIdleTimeSeconds, "Database pool max idle time"); + PropertyAssert.positive(initialSize, "Database pool initial size"); + PropertyAssert.positive(maxSize, "Database pool max size"); this(Duration.ofSeconds(maxIdleTimeSeconds), initialSize, maxSize); } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutProperties.java similarity index 58% rename from authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java rename to authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutProperties.java index fdfc6320..3165fc3e 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeouts.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseTimeoutProperties.java @@ -2,11 +2,11 @@ import javasabr.mqtt.base.util.PropertyAssert; -public record DatabaseTimeouts(String lockTimeout, String statementTimeout) { +public record DatabaseTimeoutProperties(String lockTimeout, String statementTimeout) { - public DatabaseTimeouts(int lockTimeoutSeconds, int statementTimeoutSeconds) { - PropertyAssert.positive(lockTimeoutSeconds, "Database lock timeout '%s' is not valid"); - PropertyAssert.positive(statementTimeoutSeconds, "Database statement timeout '%s' is not valid"); + public DatabaseTimeoutProperties(int lockTimeoutSeconds, int statementTimeoutSeconds) { + PropertyAssert.positive(lockTimeoutSeconds, "Database lock timeout"); + PropertyAssert.positive(statementTimeoutSeconds, "Database statement timeout"); this("%ss".formatted(lockTimeoutSeconds), "%ss".formatted(statementTimeoutSeconds)); } diff --git a/authentication-service/build.gradle b/authentication-service/build.gradle index cade8c59..24be9a40 100644 --- a/authentication-service/build.gradle +++ b/authentication-service/build.gradle @@ -14,8 +14,6 @@ dependencies { compileOnlyApi projects.credentialsSourceFile compileOnlyApi projects.authenticationProviderBasic implementation libs.springboot.starter.autoconfigure - implementation libs.springboot.starter.log4j2 - implementation libs.rlib.logger.slf4j testImplementation projects.credentialsSourceDb testImplementation projects.credentialsSourceFile diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 393e6d48..36be25c8 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -16,7 +16,6 @@ import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -50,8 +49,9 @@ public Mono authenticate(MqttCredentials mqttCredentials) { if (mqttCredentials.isAnonymous()) { return Mono.just(allowAnonymous); } else if (mqttCredentials.isMethodDefined()) { - AuthenticationProvider provider = availableProvidersMap.get(mqttCredentials.authenticationMethod()); - return provider == null ? Mono.just(false) : authenticateSafe(provider, mqttCredentials); + AuthenticationMethod authenticationMethod = mqttCredentials.authenticationMethod(); + AuthenticationProvider provider = availableProvidersMap.get(authenticationMethod); + return provider == null ? returnFalseAndLog(mqttCredentials) : authenticateSafe(provider, mqttCredentials); } else { return Flux.fromIterable(availableProvidersArray) .filter(provider -> provider.supports(mqttCredentials)) @@ -60,23 +60,34 @@ public Mono authenticate(MqttCredentials mqttCredentials) { } } - private Mono authenticateSafe(AuthenticationProvider provider, MqttCredentials request) { - return provider.authenticate(request) - .onErrorResume(exception -> onAuthenticationProviderErrorHandler(provider, exception)); + private Mono returnFalseAndLog(MqttCredentials request) { + log.debug(request.clientId(), request.authenticationMethod(), + "%s Uses unsupported authentication method '%s'"::formatted); + return Mono.just(false); + } + + private Mono authenticateSafe(AuthenticationProvider provider, MqttCredentials mqttCredentials) { + return provider.authenticate(mqttCredentials) + .onErrorResume(exception -> onAuthenticationProviderErrorHandler(provider, mqttCredentials, exception)); } private static AuthenticationProvider onDuplicateProviderErrorHandler( AuthenticationProvider first, AuthenticationProvider second) { - throw new AuthenticationConfigException("There are several [%s] authentication providers" + throw new AuthenticationConfigException("Duplicate authentication provider [%s] found" .formatted(first.getAuthenticationMethod())); } private static Mono onAuthenticationProviderErrorHandler( - @Nullable AuthenticationProvider provider, + AuthenticationProvider provider, + MqttCredentials mqttCredentials, Throwable exception) { - String authenticationMethod = provider == null ? null : provider.getAuthenticationMethod().value(); - log.error("%s authentication provider threw an error: %s".formatted(authenticationMethod, exception.getMessage())); + String clientId = mqttCredentials.clientId(); + String authenticationMethod = provider.getAuthenticationMethod().value(); + log.error("%s Authentication provider '%s' threw an error: %s".formatted( + clientId, + authenticationMethod, + exception.getMessage())); return Mono.just(false); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 4af84f5e..d86c5e72 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -26,7 +26,7 @@ public class AuthenticationServiceSpringConfig { AuthenticationService authenticationService( List availableProviders, @Value("${authentication.provider.anonymous.enabled}") boolean allowAnonymous) { - log.info("Initializing AuthenticationService..."); + log.info("Initializing DefaultAuthenticationService..."); return new DefaultAuthenticationService(availableProviders, allowAnonymous); } @@ -34,6 +34,7 @@ AuthenticationService authenticationService( @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { + log.info("Initializing BasicAuthenticationProvider..."); return new BasicAuthenticationProvider(configuredCredentialsSources); } } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 95c5f6b7..2d806f40 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -4,9 +4,10 @@ import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import javasabr.mqtt.auth.api.database.DatabaseTimeouts; +import javasabr.mqtt.auth.api.database.DatabaseTimeoutProperties; import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FlywayBuilder; +import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -14,6 +15,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +@CustomLog @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "authentication.credentials-source.database.enabled", havingValue = "true") @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource") @@ -24,6 +26,7 @@ DatabasePoolProperties credentialsSourceDatabasePoolProperties( @Value("${authentication.credentials-source.database.pool.max-idle-time-seconds}") int maxIdleTimeSeconds, @Value("${authentication.credentials-source.database.pool.initial-size}") int initialSize, @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize) { + log.info("Initializing DatabasePoolProperties..."); return new DatabasePoolProperties(maxIdleTimeSeconds, initialSize, maxSize); } @@ -33,24 +36,27 @@ DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties( @Value("${authentication.credentials-source.database.host}") String host, @Value("${authentication.credentials-source.database.port}") int port, @Value("${authentication.credentials-source.database.name}") String name) { + log.info("Initializing DatabaseConnectionProperties..."); return new DatabaseConnectionProperties(driver, host, port, name); } @Bean - DatabaseTimeouts credentialsSourceDatabaseTimeoutsProperties( + DatabaseTimeoutProperties credentialsSourceDatabaseTimeoutsProperties( @Value("${authentication.credentials-source.database.timeout.lock-timeout-seconds}") int lockTimeoutSeconds, @Value("${authentication.credentials-source.database.timeout.statement-timeout-seconds}") int statementTimeoutSeconds) { - return new DatabaseTimeouts(lockTimeoutSeconds, statementTimeoutSeconds); + log.info("Initializing DatabaseTimeoutProperties..."); + return new DatabaseTimeoutProperties(lockTimeoutSeconds, statementTimeoutSeconds); } @Bean @DependsOn("credentialsSourceFlyway") CredentialsSource databaseCredentialsSource( DatabasePoolProperties databasePoolProperties, - DatabaseTimeouts databaseTimeoutsProperties, + DatabaseTimeoutProperties databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { + log.info("Initializing DatabaseCredentialsSource..."); return DatabaseCredentialsSource.builder() .databasePoolProperties(databasePoolProperties) .databaseTimeoutsProperties(databaseTimeoutsProperties) @@ -63,6 +69,7 @@ CredentialsSource databaseCredentialsSource( org.flywaydb.core.Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { + log.info("Initializing Flyway..."); return FlywayBuilder.create() .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index 7215a3dc..1e64499e 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -3,12 +3,14 @@ import java.net.URI; import javasabr.mqtt.auth.api.file.FileProperties; import javasabr.mqtt.auth.credentials.source.FileCredentialsSource; +import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +@CustomLog @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "javasabr.mqtt.auth.credentials.source.FileCredentialsSource") @ConditionalOnProperty(name = "authentication.credentials-source.file.enabled", havingValue = "true") @@ -17,11 +19,13 @@ public class FileCredentialsSourceSpringConfig { @Bean FileProperties fileCredentialsSourceProperties( @Value("${authentication.credentials-source.file.path}") URI path) { + log.info("Initializing FileProperties..."); return new FileProperties(path); } @Bean(initMethod = "init") FileCredentialsSource fileCredentialsSource(FileProperties fileCredentialsSourceProperties) { + log.info("Initializing FileCredentialsSource..."); return new FileCredentialsSource(fileCredentialsSourceProperties.path()); } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy index 6fec1f5e..0755d357 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationServiceTest.groovy @@ -29,8 +29,9 @@ class AuthenticationServiceTest extends IntegrationSpecification { def "should authenticate credentials according test database"() { given: + def clientId = "clientId" def passwordBytes = password.getBytes(StandardCharsets.UTF_8) - def request = new MqttCredentials(userName, passwordBytes, null, new byte[0]) + def request = new MqttCredentials(clientId, userName, passwordBytes, null, new byte[0]) when: def result = authenticationService.authenticate(request).block() then: diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index 1667b439..4f351ba1 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -24,6 +24,7 @@ public DatabaseCredentials adminDatabaseCredentials() { @Bean(initMethod = "start", destroyMethod = "stop") public PostgreSQLContainer postgreSQLContainer() { + //noinspection resource return new PostgreSQLContainer("postgres:9.6.12") .withDatabaseName("testdb") .withUsername("user") diff --git a/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java b/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java index 6997370c..a80ca882 100644 --- a/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java +++ b/base/src/main/java/javasabr/mqtt/base/util/PropertyAssert.java @@ -4,21 +4,21 @@ public class PropertyAssert { - public static void positive(int value, String message) { + public static void positive(int value, String property) { if (value <= 0) { - throw new BrokerConfigurationException(message.formatted(value)); + throw new BrokerConfigurationException("%s '%s' is not valid".formatted(property, value)); } } - public static void notEmpty(String value, String message) { + public static void notEmpty(String value, String property) { if (StringUtils.isEmpty(value)) { - throw new BrokerConfigurationException(message); + throw new BrokerConfigurationException("%s is not specified".formatted(property)); } } - public static void notNull(Object value, String message) { + public static void notNull(Object value, String message, Object... args) { if (value == null) { - throw new BrokerConfigurationException(message); + throw new BrokerConfigurationException(message.formatted(args)); } } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 599c84f0..11b49565 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -85,7 +85,9 @@ protected void processMessageWithValidFields( MqttConnection connection, ExternalNetworkMqttUser user, ConnectMqttInMessage message) { + MqttCredentials mqttCredentials = new MqttCredentials( + message.clientId(), message.username(), message.password(), AuthenticationMethod.fromValue(message.authenticationMethod()), diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 751b5083..f1e89150 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -22,7 +22,7 @@ import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; import javasabr.mqtt.auth.api.database.DatabasePoolProperties; -import javasabr.mqtt.auth.api.database.DatabaseTimeouts; +import javasabr.mqtt.auth.api.database.DatabaseTimeoutProperties; import lombok.AccessLevel; import lombok.Builder; import lombok.experimental.FieldDefaults; @@ -46,7 +46,7 @@ public class DatabaseCredentialsSource implements CredentialsSource { @Builder public DatabaseCredentialsSource( DatabasePoolProperties databasePoolProperties, - DatabaseTimeouts databaseTimeoutsProperties, + DatabaseTimeoutProperties databaseTimeoutsProperties, DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { Map timeoutOptions = Map.of( diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java index bf3ac783..ddc31594 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java @@ -8,7 +8,7 @@ public class FlywayBuilder { @SuppressWarnings("unused") - @Builder(builderMethodName = "create", builderClassName = "FlywaySupport") + @Builder(builderMethodName = "create", builderClassName = "InnerBuilder") private static Flyway createFlyway( DatabaseConnectionProperties databaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { From 1b544a91cc6b43f6f8380e5a9a194864479affa1 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:44:44 +0100 Subject: [PATCH 099/107] [broker-141] Improve logging and naming --- .../mqtt/auth/service/DefaultAuthenticationService.java | 6 ++++-- .../service/config/AuthenticationServiceSpringConfig.java | 2 +- .../config/DatabaseCredentialsSourceSpringConfig.java | 8 ++++---- .../service/config/FileCredentialsSourceSpringConfig.java | 2 +- .../application/service/DatabaseTestSpringConfig.java | 2 +- .../credentials/source/DatabaseCredentialsSource.java | 6 ++---- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 36be25c8..59883155 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -51,7 +51,9 @@ public Mono authenticate(MqttCredentials mqttCredentials) { } else if (mqttCredentials.isMethodDefined()) { AuthenticationMethod authenticationMethod = mqttCredentials.authenticationMethod(); AuthenticationProvider provider = availableProvidersMap.get(authenticationMethod); - return provider == null ? returnFalseAndLog(mqttCredentials) : authenticateSafe(provider, mqttCredentials); + return provider == null + ? onAuthenticationProviderNotFoundHandler(mqttCredentials) + : authenticateSafe(provider, mqttCredentials); } else { return Flux.fromIterable(availableProvidersArray) .filter(provider -> provider.supports(mqttCredentials)) @@ -60,7 +62,7 @@ public Mono authenticate(MqttCredentials mqttCredentials) { } } - private Mono returnFalseAndLog(MqttCredentials request) { + private Mono onAuthenticationProviderNotFoundHandler(MqttCredentials request) { log.debug(request.clientId(), request.authenticationMethod(), "%s Uses unsupported authentication method '%s'"::formatted); return Mono.just(false); diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index d86c5e72..47b8ea3c 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -26,7 +26,7 @@ public class AuthenticationServiceSpringConfig { AuthenticationService authenticationService( List availableProviders, @Value("${authentication.provider.anonymous.enabled}") boolean allowAnonymous) { - log.info("Initializing DefaultAuthenticationService..."); + log.info("Initializing AuthenticationService..."); return new DefaultAuthenticationService(availableProviders, allowAnonymous); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 2d806f40..dc7ed787 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -26,7 +26,7 @@ DatabasePoolProperties credentialsSourceDatabasePoolProperties( @Value("${authentication.credentials-source.database.pool.max-idle-time-seconds}") int maxIdleTimeSeconds, @Value("${authentication.credentials-source.database.pool.initial-size}") int initialSize, @Value("${authentication.credentials-source.database.pool.max-size}") int maxSize) { - log.info("Initializing DatabasePoolProperties..."); + log.info("Initializing CredentialsSourceDatabasePoolProperties..."); return new DatabasePoolProperties(maxIdleTimeSeconds, initialSize, maxSize); } @@ -36,7 +36,7 @@ DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties( @Value("${authentication.credentials-source.database.host}") String host, @Value("${authentication.credentials-source.database.port}") int port, @Value("${authentication.credentials-source.database.name}") String name) { - log.info("Initializing DatabaseConnectionProperties..."); + log.info("Initializing CredentialsSourceDatabaseConnectionProperties..."); return new DatabaseConnectionProperties(driver, host, port, name); } @@ -45,7 +45,7 @@ DatabaseTimeoutProperties credentialsSourceDatabaseTimeoutsProperties( @Value("${authentication.credentials-source.database.timeout.lock-timeout-seconds}") int lockTimeoutSeconds, @Value("${authentication.credentials-source.database.timeout.statement-timeout-seconds}") int statementTimeoutSeconds) { - log.info("Initializing DatabaseTimeoutProperties..."); + log.info("Initializing CredentialsSourceDatabaseTimeoutsProperties..."); return new DatabaseTimeoutProperties(lockTimeoutSeconds, statementTimeoutSeconds); } @@ -69,7 +69,7 @@ CredentialsSource databaseCredentialsSource( org.flywaydb.core.Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { - log.info("Initializing Flyway..."); + log.info("Initializing CredentialsSourceFlyway..."); return FlywayBuilder.create() .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index 1e64499e..2dd107fd 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -19,7 +19,7 @@ public class FileCredentialsSourceSpringConfig { @Bean FileProperties fileCredentialsSourceProperties( @Value("${authentication.credentials-source.file.path}") URI path) { - log.info("Initializing FileProperties..."); + log.info("Initializing FileCredentialsSourceProperties..."); return new FileProperties(path); } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java index 4f351ba1..d0fa9aaf 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java @@ -32,7 +32,7 @@ public PostgreSQLContainer postgreSQLContainer() { } @Bean - public DynamicPropertyRegistrar configurePostgresSQLProperties(PostgreSQLContainer container) { + public DynamicPropertyRegistrar configurePostgresSQLContainerPort(PostgreSQLContainer container) { return registry -> registry.add( "authentication.credentials-source.database.port", () -> container.getMappedPort(5432)); diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index f1e89150..270c0572 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -89,13 +89,11 @@ public String toString() { } private Mono executeCredentialsQuery(Connection connection, MqttCredentials credentials) { - Publisher credentialsQuery = connection.createStatement(CREDENTIALS_QUERY) + Publisher credentialsQueryPublisher = connection.createStatement(CREDENTIALS_QUERY) .bind("$1", credentials.username()) .bind("$2", credentials.password()) .execute(); - return Mono.from(credentialsQuery) - .flatMap(DatabaseCredentialsSource::isCredentialsRecordFound) - .defaultIfEmpty(false); + return Mono.from(credentialsQueryPublisher).flatMap(DatabaseCredentialsSource::isCredentialsRecordFound); } private static Mono isCredentialsRecordFound(Result result) { From 98f3cd28fc497b9502abb3abeed170c9fe908c56 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:18:40 +0100 Subject: [PATCH 100/107] [broker-141] Refactoring --- .../mqtt/auth/api/file/FileProperties.java | 9 +--- .../service/DefaultAuthenticationService.java | 20 ++++---- ...DatabaseCredentialsSourceSpringConfig.java | 15 +++--- .../service/DatabaseTestSpringConfig.groovy | 47 +++++++++++++++++++ .../service/DatabaseTestSpringConfig.java | 40 ---------------- .../service/IntegrationSpecification.groovy | 1 - .../source/DatabaseCredentialsSource.java | 15 +++--- 7 files changed, 72 insertions(+), 75 deletions(-) create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy delete mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java index 8e9b6a8d..29357fbb 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/file/FileProperties.java @@ -1,12 +1,5 @@ package javasabr.mqtt.auth.api.file; import java.net.URI; -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException; -public record FileProperties(URI path) { - public FileProperties { - if (path == null) { - throw new AuthenticationConfigException("File path cannot be null"); - } - } -} +public record FileProperties(URI path) {} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index 59883155..cef82a4c 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -23,8 +23,8 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class DefaultAuthenticationService implements AuthenticationService { - Map availableProvidersMap; - Array availableProvidersArray; + Map authMethodToProvider; + Array providers; boolean allowAnonymous; public DefaultAuthenticationService(List configuredProviders, boolean allowAnonymous) { @@ -32,16 +32,16 @@ public DefaultAuthenticationService(List configuredProvi throw new AuthenticationConfigException("Authenticator providers are not configured"); } this.allowAnonymous = allowAnonymous; - this.availableProvidersMap = configuredProviders.stream() + this.authMethodToProvider = configuredProviders.stream() .collect(Collectors.toMap( AuthenticationProvider::getAuthenticationMethod, Function.identity(), DefaultAuthenticationService::onDuplicateProviderErrorHandler, () -> new EnumMap<>(AuthenticationMethod.class))); - this.availableProvidersArray = configuredProviders.stream() + this.providers = configuredProviders.stream() .sorted(Comparator.comparingInt(provider -> provider.getAuthenticationMethod().priority())) .collect(ArrayCollectors.toArray(AuthenticationProvider.class)); - log.info(this.availableProvidersMap, DefaultAuthenticationService::buildServiceDescription); + log.info(this.authMethodToProvider, DefaultAuthenticationService::buildServiceDescription); } @Override @@ -50,14 +50,14 @@ public Mono authenticate(MqttCredentials mqttCredentials) { return Mono.just(allowAnonymous); } else if (mqttCredentials.isMethodDefined()) { AuthenticationMethod authenticationMethod = mqttCredentials.authenticationMethod(); - AuthenticationProvider provider = availableProvidersMap.get(authenticationMethod); + AuthenticationProvider provider = authMethodToProvider.get(authenticationMethod); return provider == null ? onAuthenticationProviderNotFoundHandler(mqttCredentials) - : authenticateSafe(provider, mqttCredentials); + : tryToAuthenticate(provider, mqttCredentials); } else { - return Flux.fromIterable(availableProvidersArray) + return Flux.fromIterable(providers) .filter(provider -> provider.supports(mqttCredentials)) - .concatMap(provider -> authenticateSafe(provider, mqttCredentials)) + .concatMap(provider -> tryToAuthenticate(provider, mqttCredentials)) .any(Boolean::booleanValue); } } @@ -68,7 +68,7 @@ private Mono onAuthenticationProviderNotFoundHandler(MqttCredentials re return Mono.just(false); } - private Mono authenticateSafe(AuthenticationProvider provider, MqttCredentials mqttCredentials) { + private Mono tryToAuthenticate(AuthenticationProvider provider, MqttCredentials mqttCredentials) { return provider.authenticate(mqttCredentials) .onErrorResume(exception -> onAuthenticationProviderErrorHandler(provider, mqttCredentials, exception)); } diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index dc7ed787..44879756 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -8,6 +8,7 @@ import javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource; import javasabr.mqtt.auth.credentials.source.FlywayBuilder; import lombok.CustomLog; +import org.flywaydb.core.Flyway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -52,21 +53,21 @@ DatabaseTimeoutProperties credentialsSourceDatabaseTimeoutsProperties( @Bean @DependsOn("credentialsSourceFlyway") CredentialsSource databaseCredentialsSource( - DatabasePoolProperties databasePoolProperties, - DatabaseTimeoutProperties databaseTimeoutsProperties, - DatabaseConnectionProperties databaseConnectionProperties, + DatabasePoolProperties credentialsSourceDatabasePoolProperties, + DatabaseTimeoutProperties credentialsSourceDatabaseTimeoutsProperties, + DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials readerDatabaseCredentials) { log.info("Initializing DatabaseCredentialsSource..."); return DatabaseCredentialsSource.builder() - .databasePoolProperties(databasePoolProperties) - .databaseTimeoutsProperties(databaseTimeoutsProperties) - .databaseConnectionProperties(databaseConnectionProperties) + .databasePoolProperties(credentialsSourceDatabasePoolProperties) + .databaseTimeoutsProperties(credentialsSourceDatabaseTimeoutsProperties) + .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .readerDatabaseCredentials(readerDatabaseCredentials) .build(); } @Bean(initMethod = "migrate") - org.flywaydb.core.Flyway credentialsSourceFlyway( + Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, DatabaseCredentials adminDatabaseCredentials) { log.info("Initializing CredentialsSourceFlyway..."); diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy new file mode 100644 index 00000000..897aa6ff --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.groovy @@ -0,0 +1,47 @@ +//file:noinspection GrMethodMayBeStatic +package javasabr.mqtt.broker.application.service + +import javasabr.mqtt.auth.api.database.DatabaseCredentials +import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.test.context.DynamicPropertyRegistrar +import org.testcontainers.postgresql.PostgreSQLContainer +import org.testcontainers.utility.DockerImageName + +@Configuration +@Import(AuthenticationServiceSpringConfig.class) +class DatabaseTestSpringConfig { + + private static final String POSTGRES_VERSION = "18.1" + + @Bean + DatabaseCredentials readerDatabaseCredentials(PostgreSQLContainer _) { + return new DatabaseCredentials("user", "pass") + } + + @Bean + DatabaseCredentials adminDatabaseCredentials(PostgreSQLContainer _) { + return new DatabaseCredentials("user", "pass") + } + + @Bean(initMethod = "start", destroyMethod = "stop") + PostgreSQLContainer postgresSQLContainer() { + return new PostgreSQLContainer(DockerImageName + .parse(PostgreSQLContainer.IMAGE) + .withTag(POSTGRES_VERSION)) + .withDatabaseName("testdb") + .withUsername("user") + .withPassword("pass") + } + + @Bean + DynamicPropertyRegistrar configurePostgresSQLContainerPort(PostgreSQLContainer container) { + return { registry -> + registry.add( + "authentication.credentials-source.database.port", + () -> container.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT)) + } + } +} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java deleted file mode 100644 index d0fa9aaf..00000000 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/DatabaseTestSpringConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package javasabr.mqtt.broker.application.service; - -import javasabr.mqtt.auth.api.database.DatabaseCredentials; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; -import org.springframework.test.context.DynamicPropertyRegistrar; -import org.testcontainers.postgresql.PostgreSQLContainer; - -@Configuration -public class DatabaseTestSpringConfig { - - @Bean - @DependsOn("postgreSQLContainer") - public DatabaseCredentials readerDatabaseCredentials() { - return new DatabaseCredentials("user", ""); - } - - @Bean - @DependsOn("postgreSQLContainer") - public DatabaseCredentials adminDatabaseCredentials() { - return new DatabaseCredentials("user", ""); - } - - @Bean(initMethod = "start", destroyMethod = "stop") - public PostgreSQLContainer postgreSQLContainer() { - //noinspection resource - return new PostgreSQLContainer("postgres:9.6.12") - .withDatabaseName("testdb") - .withUsername("user") - .withPassword(""); - } - - @Bean - public DynamicPropertyRegistrar configurePostgresSQLContainerPort(PostgreSQLContainer container) { - return registry -> registry.add( - "authentication.credentials-source.database.port", - () -> container.getMappedPort(5432)); - } -} diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy index 4637be2f..a2d5684d 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/IntegrationSpecification.groovy @@ -7,7 +7,6 @@ import spock.lang.Specification @TestPropertySource("classpath:application-test.properties") @SpringJUnitConfig(classes = [ - AuthenticationServiceSpringConfig, DatabaseTestSpringConfig ]) class IntegrationSpecification extends Specification { diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 270c0572..36fb1b31 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -14,7 +14,6 @@ import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; -import io.r2dbc.spi.Result; import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.api.CredentialsSourceType; @@ -26,7 +25,6 @@ import lombok.AccessLevel; import lombok.Builder; import lombok.experimental.FieldDefaults; -import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @@ -89,14 +87,13 @@ public String toString() { } private Mono executeCredentialsQuery(Connection connection, MqttCredentials credentials) { - Publisher credentialsQueryPublisher = connection.createStatement(CREDENTIALS_QUERY) + return Mono.from(connection + .createStatement(CREDENTIALS_QUERY) .bind("$1", credentials.username()) .bind("$2", credentials.password()) - .execute(); - return Mono.from(credentialsQueryPublisher).flatMap(DatabaseCredentialsSource::isCredentialsRecordFound); - } - - private static Mono isCredentialsRecordFound(Result result) { - return Mono.from(result.map((_, _) -> true)).defaultIfEmpty(false); + .execute()) + .map(result -> result.map((_, _) -> true)) + .flatMap(Mono::from) + .defaultIfEmpty(false); } } From bea01794121e7e45454a4657e683f37c6462b434 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:52:51 +0100 Subject: [PATCH 101/107] [broker-141] Initialize FileCredentialsSource in constructor --- .../config/FileCredentialsSourceSpringConfig.java | 2 +- .../auth/credentials/source/FileCredentialsSource.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java index 2dd107fd..6deffa4a 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.java @@ -23,7 +23,7 @@ FileProperties fileCredentialsSourceProperties( return new FileProperties(path); } - @Bean(initMethod = "init") + @Bean FileCredentialsSource fileCredentialsSource(FileProperties fileCredentialsSourceProperties) { log.info("Initializing FileCredentialsSource..."); return new FileCredentialsSource(fileCredentialsSourceProperties.path()); diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 22b7348e..882e8002 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -7,16 +7,20 @@ import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -@RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class FileCredentialsSource extends InMemoryCredentialsSource { URI fileName; - public void init() { + public FileCredentialsSource(URI fileName) { + this.fileName = fileName; + init(); + } + + private void init() { Path path = Path.of(fileName); if (!Files.exists(path)) { throw new CredentialsSourceException("Credentials file:[%s] could not be found".formatted(fileName)); From 56cc9f115d201d5c2aa7a539e3f70a93b4b4327e Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:53:18 +0100 Subject: [PATCH 102/107] [broker-141] Introduce NoOpAuthenticationService --- .../service/NoOpAuthenticationService.java | 13 +++++++++++ .../AuthenticationServiceSpringConfig.java | 23 ++++++++++++++----- .../service/AuthenticationProviderTest.groovy | 15 +----------- 3 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 authentication-service/src/main/java/javasabr/mqtt/auth/service/NoOpAuthenticationService.java diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/NoOpAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/NoOpAuthenticationService.java new file mode 100644 index 00000000..b4767b07 --- /dev/null +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/NoOpAuthenticationService.java @@ -0,0 +1,13 @@ +package javasabr.mqtt.auth.service; + +import javasabr.mqtt.auth.api.AuthenticationService; +import javasabr.mqtt.auth.api.MqttCredentials; +import reactor.core.publisher.Mono; + +public class NoOpAuthenticationService implements AuthenticationService { + + @Override + public Mono authenticate(MqttCredentials mqttCredentials) { + return Mono.just(true); + } +} diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java index 47b8ea3c..8e3b88e9 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.java @@ -6,9 +6,12 @@ import javasabr.mqtt.auth.api.CredentialsSource; import javasabr.mqtt.auth.provider.BasicAuthenticationProvider; import javasabr.mqtt.auth.service.DefaultAuthenticationService; +import javasabr.mqtt.auth.service.NoOpAuthenticationService; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,18 +26,26 @@ public class AuthenticationServiceSpringConfig { @Bean + @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") + @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") + AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { + log.info("Initializing BasicAuthenticationProvider..."); + return new BasicAuthenticationProvider(configuredCredentialsSources); + } + + @Bean + @ConditionalOnBean(AuthenticationProvider.class) AuthenticationService authenticationService( List availableProviders, - @Value("${authentication.provider.anonymous.enabled}") boolean allowAnonymous) { + @Value("${authentication.provider.anonymous.enabled:false}") boolean allowAnonymous) { log.info("Initializing AuthenticationService..."); return new DefaultAuthenticationService(availableProviders, allowAnonymous); } @Bean - @ConditionalOnClass(name = "javasabr.mqtt.auth.provider.BasicAuthenticationProvider") - @ConditionalOnProperty(name = "authentication.provider.basic.enabled", havingValue = "true") - AuthenticationProvider basicAuthenticationProvider(List configuredCredentialsSources) { - log.info("Initializing BasicAuthenticationProvider..."); - return new BasicAuthenticationProvider(configuredCredentialsSources); + @ConditionalOnMissingBean(AuthenticationService.class) + AuthenticationService noOpAuthenticationService() { + log.info("Initializing NoOpAuthenticationService..."); + return new NoOpAuthenticationService(); } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 2af72762..5bc67d69 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -3,7 +3,6 @@ package javasabr.mqtt.broker.application.service import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.CredentialsSource -import javasabr.mqtt.auth.api.exception.AuthenticationConfigException import javasabr.mqtt.auth.credentials.source.FileCredentialsSource import javasabr.mqtt.auth.provider.BasicAuthenticationProvider import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig @@ -64,19 +63,7 @@ class AuthenticationProviderTest extends IntegrationSpecification { } }) then: - def exception = thrown(Exception) - with(rootCauseOf(exception)) { rootCause -> - assert rootCause instanceof AuthenticationConfigException - assert message == "Authenticator providers are not configured" - } - } - } - - static Throwable rootCauseOf(Throwable throwable) { - Throwable rootCause = throwable - while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { - rootCause = rootCause.getCause() + noExceptionThrown() } - return rootCause } } From aac1ce9b767d7022843d41221f654450d2af84c9 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:10:07 +0100 Subject: [PATCH 103/107] [broker-141] Replace EmptyProviderTest with NoOpAuthenticationServiceTest --- .../service/AuthenticationProviderTest.groovy | 66 +++++-------------- .../NoOpAuthenticationServiceTest.groovy | 29 ++++++++ 2 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/NoOpAuthenticationServiceTest.groovy diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy index 5bc67d69..c22f7583 100644 --- a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/AuthenticationProviderTest.groovy @@ -5,65 +5,33 @@ import javasabr.mqtt.auth.api.AuthenticationProvider import javasabr.mqtt.auth.api.CredentialsSource import javasabr.mqtt.auth.credentials.source.FileCredentialsSource import javasabr.mqtt.auth.provider.BasicAuthenticationProvider -import javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.env.PropertiesPropertySourceLoader -import org.springframework.boot.test.context.runner.ApplicationContextRunner -import org.springframework.core.env.PropertySource -import org.springframework.core.io.ClassPathResource import org.springframework.test.context.TestPropertySource -import spock.lang.Specification import static javasabr.mqtt.auth.api.AuthenticationMethod.BASIC +@TestPropertySource(properties = [ + "authentication.provider.anonymous.enabled=false", + "authentication.provider.basic.enabled=true", + "authentication.credentials-source.file.enabled=true", + "authentication.provider.default.method=basic" +]) class AuthenticationProviderTest extends IntegrationSpecification { @Autowired List authenticationProviders - @TestPropertySource(properties = [ - "authentication.provider.anonymous.enabled=false", - "authentication.provider.basic.enabled=true", - "authentication.credentials-source.file.enabled=true", - "authentication.provider.default.method=basic" - ]) - static class FileCredentialsSourceTest extends AuthenticationProviderTest { - @Autowired - CredentialsSource credentialsSource - - def "should create file credentials source and basic authentication provider"() { - expect: - (credentialsSource instanceof FileCredentialsSource) - and: - verifyEach(authenticationProviders) { provider -> - provider.authenticationMethod == BASIC - provider instanceof BasicAuthenticationProvider - } - } + @Autowired + CredentialsSource credentialsSource + + def "should create file credentials source and basic authentication provider"() { + expect: + (credentialsSource instanceof FileCredentialsSource) + and: + verifyEach(authenticationProviders) { provider -> + provider.authenticationMethod == BASIC + provider instanceof BasicAuthenticationProvider + } } - static class EmptyProviderTest extends Specification { - - def "should fail start application context without any authentication provider"() { - given: - PropertySource propertySource = new PropertiesPropertySourceLoader() - .load("test-props", new ClassPathResource("application-test.properties")).getFirst() - def appContext = new ApplicationContextRunner() - .withAllowBeanDefinitionOverriding(true) - .withUserConfiguration(AuthenticationServiceSpringConfig) - .withInitializer { context -> - context.getEnvironment().getPropertySources().addLast(propertySource) - } - when: - appContext - .withPropertyValues("authentication.provider.anonymous.enabled=false") - .run({ context -> - if (context.startupFailure) { - throw context.startupFailure - } - }) - then: - noExceptionThrown() - } - } } diff --git a/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/NoOpAuthenticationServiceTest.groovy b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/NoOpAuthenticationServiceTest.groovy new file mode 100644 index 00000000..d65140f3 --- /dev/null +++ b/authentication-service/src/test/groovy/javasabr/mqtt/broker/application/service/NoOpAuthenticationServiceTest.groovy @@ -0,0 +1,29 @@ +package javasabr.mqtt.broker.application.service + +import javasabr.mqtt.auth.api.AuthenticationService +import javasabr.mqtt.auth.api.MqttCredentials +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.TestPropertySource + +import java.nio.charset.StandardCharsets + +@TestPropertySource(properties = [ + "authentication.provider.anonymous.enabled=false" +]) +class NoOpAuthenticationServiceTest extends IntegrationSpecification { + + @Autowired + AuthenticationService authenticationService + + def "should authenticate credentials according test database"() { + given: + def clientId = "clientId" + def userName = "any_user_name" + def passwordBytes = "any_password".getBytes(StandardCharsets.UTF_8) + def request = new MqttCredentials(clientId, userName, passwordBytes, null, new byte[0]) + when: + def result = authenticationService.authenticate(request) + then: + result.block() + } +} From 51d3462edb91c765e363715587b6721ff6bf6983 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 18 Jan 2026 13:45:46 +0100 Subject: [PATCH 104/107] [broker-141] Rename method --- .../auth/credentials/source/DatabaseCredentialsSource.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index 36fb1b31..e1eda79d 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -57,7 +57,8 @@ public DatabaseCredentialsSource( .option(PORT, databaseConnectionProperties.port()) .option(USER, readerDatabaseCredentials.username()) .option(PASSWORD, readerDatabaseCredentials.password()) - .option(OPTIONS, timeoutOptions).build(); + .option(OPTIONS, timeoutOptions) + .build(); ConnectionFactory connectionFactory = ConnectionFactories.get(connectionFactoryOptions); ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) .maxIdleTime(databasePoolProperties.maxIdleTime()) @@ -76,7 +77,7 @@ public CredentialsSourceType getType() { public Mono isCredentialsValid(MqttCredentials credentials) { return Mono.usingWhen( connectionFactory.create(), - connection -> executeCredentialsQuery(connection, credentials), + connection -> verifyCredentials(connection, credentials), Connection::close); } @@ -86,7 +87,7 @@ public String toString() { return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getType(), dbDriver); } - private Mono executeCredentialsQuery(Connection connection, MqttCredentials credentials) { + private Mono verifyCredentials(Connection connection, MqttCredentials credentials) { return Mono.from(connection .createStatement(CREDENTIALS_QUERY) .bind("$1", credentials.username()) From b391f93d634a5a8fadbc1579bc0a00e3620112ef Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 18 Jan 2026 14:33:59 +0100 Subject: [PATCH 105/107] [broker-141] Make database migration location configurable --- .../config/DatabaseCredentialsSourceSpringConfig.java | 5 ++++- .../credentials/source/DatabaseCredentialsSource.java | 8 ++++---- .../mqtt/auth/credentials/source/FlywayBuilder.java | 5 ++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java index 44879756..edf500ea 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.java @@ -69,11 +69,14 @@ CredentialsSource databaseCredentialsSource( @Bean(initMethod = "migrate") Flyway credentialsSourceFlyway( DatabaseConnectionProperties credentialsSourceDatabaseConnectionProperties, - DatabaseCredentials adminDatabaseCredentials) { + DatabaseCredentials adminDatabaseCredentials, + @Value("${authentication.credentials-source.database.migration.location:classpath:db/migration}") + String databaseMigrationLocation) { log.info("Initializing CredentialsSourceFlyway..."); return FlywayBuilder.create() .databaseConnectionProperties(credentialsSourceDatabaseConnectionProperties) .adminDatabaseCredentials(adminDatabaseCredentials) + .databaseMigrationLocations(new String[]{databaseMigrationLocation}) .build(); } } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java index e1eda79d..ceaa4abe 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/DatabaseCredentialsSource.java @@ -39,7 +39,7 @@ public class DatabaseCredentialsSource implements CredentialsSource { LIMIT 1 """; - ConnectionFactory connectionFactory; + ConnectionPool connectionPool; @Builder public DatabaseCredentialsSource( @@ -65,7 +65,7 @@ public DatabaseCredentialsSource( .maxSize(databasePoolProperties.maxSize()) .initialSize(databasePoolProperties.initialSize()) .build(); - this.connectionFactory = new ConnectionPool(configuration); + this.connectionPool = new ConnectionPool(configuration); } @Override @@ -76,14 +76,14 @@ public CredentialsSourceType getType() { @Override public Mono isCredentialsValid(MqttCredentials credentials) { return Mono.usingWhen( - connectionFactory.create(), + connectionPool.create(), connection -> verifyCredentials(connection, credentials), Connection::close); } @Override public String toString() { - String dbDriver = connectionFactory.getMetadata().getName(); + String dbDriver = connectionPool.getMetadata().getName(); return "{ \"credentialsSource\": \"%s\", \"databaseDriver\": \"%s\" }".formatted(getType(), dbDriver); } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java index ddc31594..342e4eaa 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java @@ -1,5 +1,6 @@ package javasabr.mqtt.auth.credentials.source; +import java.util.List; import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; import lombok.Builder; @@ -11,7 +12,8 @@ public class FlywayBuilder { @Builder(builderMethodName = "create", builderClassName = "InnerBuilder") private static Flyway createFlyway( DatabaseConnectionProperties databaseConnectionProperties, - DatabaseCredentials adminDatabaseCredentials) { + DatabaseCredentials adminDatabaseCredentials, + String[] databaseMigrationLocations) { String databaseUrl = "jdbc:%s://%s:%s/%s".formatted( databaseConnectionProperties.driver().value(), databaseConnectionProperties.host(), @@ -19,6 +21,7 @@ private static Flyway createFlyway( databaseConnectionProperties.dbName()); return Flyway.configure() .dataSource(databaseUrl, adminDatabaseCredentials.username(), adminDatabaseCredentials.password()) + .locations(databaseMigrationLocations) .load(); } } From e059fbe6ed02780e23f87908d95ebabceae786e1 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 18 Jan 2026 15:20:57 +0100 Subject: [PATCH 106/107] [broker-141] Improve enum API --- .../mqtt/auth/api/AuthenticationMethod.java | 23 +++++++++++-------- .../DatabaseConnectionProperties.java | 2 +- .../auth/api/database/DatabaseDriver.java | 20 ++++++++-------- .../credentials/source/FlywayBuilder.java | 1 - gradle/libs.versions.toml | 2 +- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java index 20c1eb91..41864be8 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/AuthenticationMethod.java @@ -1,9 +1,9 @@ package javasabr.mqtt.auth.api; -import java.util.Arrays; -import java.util.function.Function; -import javasabr.rlib.collections.dictionary.DictionaryCollectors; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import java.util.Collection; +import java.util.List; +import javasabr.rlib.common.AliasedEnum; +import javasabr.rlib.common.util.AliasedEnumMap; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.Getter; @@ -16,20 +16,25 @@ @Accessors @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum AuthenticationMethod { +public enum AuthenticationMethod implements AliasedEnum { X509("x509", 1), JWT("jwt", 3), OAUTH2("oauth2", 4), BASIC("basic", 5), LDAP("ldap", 6); - private static final RefToRefDictionary CACHE = Arrays.stream(values()) - .collect(DictionaryCollectors.toRefToRefDictionary(AuthenticationMethod::value, Function.identity())); + public static final AliasedEnumMap MAP = new AliasedEnumMap<>(AuthenticationMethod.class); String value; int priority; - public static @Nullable AuthenticationMethod fromValue(String value) { - return StringUtils.isEmpty(value) ? null : CACHE.get(value); + @Nullable + public static AuthenticationMethod fromValue(@Nullable String value) { + return StringUtils.isEmpty(value) ? null : MAP.resolve(value); + } + + @Override + public Collection aliases() { + return List.of(value); } } diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java index 527fa0bc..8a9e5713 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseConnectionProperties.java @@ -9,7 +9,7 @@ public DatabaseConnectionProperties(String driver, String host, int port, String PropertyAssert.positive(port, "Database port"); PropertyAssert.notEmpty(dbName, "Database name"); PropertyAssert.notEmpty(driver, "Database driver"); - DatabaseDriver databaseDriver = DatabaseDriver.fromValue(driver); + DatabaseDriver databaseDriver = DatabaseDriver.BY_ALIAS.resolve(driver); PropertyAssert.notNull(databaseDriver, "Database driver '%s' is not supported", driver); this(databaseDriver, host, port, dbName); diff --git a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java index e6597516..8699ef30 100644 --- a/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java +++ b/authentication-api/src/main/java/javasabr/mqtt/auth/api/database/DatabaseDriver.java @@ -1,30 +1,28 @@ package javasabr.mqtt.auth.api.database; -import java.util.Arrays; -import java.util.function.Function; -import javasabr.rlib.collections.dictionary.DictionaryCollectors; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; -import javasabr.rlib.common.util.StringUtils; +import java.util.Collection; +import java.util.Set; +import javasabr.rlib.common.AliasedEnum; +import javasabr.rlib.common.util.AliasedEnumMap; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; @Getter @Accessors @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum DatabaseDriver { +public enum DatabaseDriver implements AliasedEnum { POSTGRESQL("postgresql"); - private static final RefToRefDictionary CACHE = Arrays.stream(values()) - .collect(DictionaryCollectors.toRefToRefDictionary(DatabaseDriver::value, Function.identity())); + public static final AliasedEnumMap BY_ALIAS = new AliasedEnumMap<>(DatabaseDriver.class); String value; - public static @Nullable DatabaseDriver fromValue(String value) { - return StringUtils.isEmpty(value) ? null : CACHE.get(value); + @Override + public Collection aliases() { + return Set.of(value); } } diff --git a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java index 342e4eaa..2e5cd4dd 100644 --- a/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java +++ b/credentials-source-db/src/main/java/javasabr/mqtt/auth/credentials/source/FlywayBuilder.java @@ -1,6 +1,5 @@ package javasabr.mqtt.auth.credentials.source; -import java.util.List; import javasabr.mqtt.auth.api.database.DatabaseConnectionProperties; import javasabr.mqtt.auth.api.database.DatabaseCredentials; import lombok.Builder; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82b2f612..f739750a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://gitlab.com/JavaSaBr/maven-repo/-/packages -rlib = "10.0.alpha9" +rlib = "10.0.alpha10" # https://mvnrepository.com/artifact/org.projectlombok/lombok lombok = "1.18.38" # https://mvnrepository.com/artifact/org.jspecify/jspecify From 27e6e836471de783c339063673d63a2b7cc1426a Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 18 Jan 2026 15:23:51 +0100 Subject: [PATCH 107/107] [broker-141] Use method reference for log printing --- .../mqtt/auth/service/DefaultAuthenticationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java index cef82a4c..cb23a1b0 100644 --- a/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java +++ b/authentication-service/src/main/java/javasabr/mqtt/auth/service/DefaultAuthenticationService.java @@ -86,10 +86,11 @@ private static Mono onAuthenticationProviderErrorHandler( Throwable exception) { String clientId = mqttCredentials.clientId(); String authenticationMethod = provider.getAuthenticationMethod().value(); - log.error("%s Authentication provider '%s' threw an error: %s".formatted( + log.error( clientId, authenticationMethod, - exception.getMessage())); + exception.getMessage(), + "%s Authentication provider '%s' threw an error: %s"::formatted); return Mono.just(false); }