diff --git a/.gitignore b/.gitignore index 9419d78..3b01119 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ # Log files # /*.log /*.log.* +/logs/** # Output folders # /test-output/ diff --git a/LICENSE b/LICENSE index 9e6d16f..850d137 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2022-2023 Bernardo Martínez Garrido +Copyright (c) 2022-2025 Bernardo Martínez Garrido Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/logs/app.log b/logs/app.log deleted file mode 100644 index fdac2e2..0000000 --- a/logs/app.log +++ /dev/null @@ -1,102 +0,0 @@ -2024-12-05T23:05:19,195 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:05:19,206 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:05:19,208 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:05:24,986 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:05:26,391 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 7.695 seconds (process running for 8.657) -2024-12-05T23:09:44,571 DEBUG [http-nio-8080-exec-2] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHORIZATION_FAILURE for anonymousUser from address 0:0:0:0:0:0:0:1 -2024-12-05T23:09:44,593 DEBUG [http-nio-8080-exec-2] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHORIZATION_FAILURE for anonymousUser with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 -2024-12-05T23:10:03,228 DEBUG [http-nio-8080-exec-3] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHORIZATION_FAILURE for anonymousUser with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 -2024-12-05T23:10:03,233 DEBUG [http-nio-8080-exec-3] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHORIZATION_FAILURE for anonymousUser with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 -2024-12-05T23:10:12,539 DEBUG [http-nio-8080-exec-4] c.b.e.s.s.w.b.s.u.PersistentUserDetailsService.loadUserByUsername(125): User admin exists -2024-12-05T23:10:12,539 DEBUG [http-nio-8080-exec-4] c.b.e.s.s.w.b.s.u.PersistentUserDetailsService.loadUserByUsername(126): Authorities for admin: [CREATE_DATA, DELETE_DATA, READ_DATA, UPDATE_DATA] -2024-12-05T23:10:12,541 DEBUG [http-nio-8080-exec-4] c.b.e.s.s.w.b.s.u.PersistentUserDetailsService.loadUserByUsername(127): User flags for admin. Enabled: true. Non expired: true. Non locked: true. Credentials non expired: true -2024-12-05T23:10:12,544 DEBUG [http-nio-8080-exec-4] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHENTICATION_SUCCESS for admin with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 -2024-12-05T23:10:16,632 WARN [http-nio-8080-exec-5] c.b.e.s.s.w.b.s.u.PersistentUserDetailsService.loadUserByUsername(119): Username noroles has no authorities -2024-12-05T23:10:16,824 WARN [http-nio-8080-exec-5] c.b.e.s.s.w.b.s.u.PersistentUserDetailsService.loadUserByUsername(119): Username noroles has no authorities -2024-12-05T23:10:16,919 DEBUG [http-nio-8080-exec-5] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHENTICATION_FAILURE for noroles with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 (Bad credentials) -2024-12-05T23:10:16,923 DEBUG [http-nio-8080-exec-5] c.b.e.s.s.w.b.s.a.AuditEventLogger.auditEventHappened(94): Audit event AUTHORIZATION_FAILURE for anonymousUser with session id E1E20214398AEFAC3C42B5562BE1F338 from address 0:0:0:0:0:0:0:1 -2024-12-05T23:10:58,623 WARN [Thread-5] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:10:58,762 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:10:58,763 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:10:58,763 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:10:59,334 WARN [restartedMain] o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext.refresh(635): Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [com.bernardomg.example.spring.security.ws.basic.login.service.DefaultLoginService] for bean with name 'defaultLoginService' defined in file [D:\repositories\spring-ws-basic-security-example\target\classes\com\bernardomg\example\spring\security\ws\basic\login\service\DefaultLoginService.class]: problem with class file or dependent class -2024-12-05T23:10:59,362 ERROR [restartedMain] o.s.b.SpringApplication.reportFailure(857): Application run failed -org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [com.bernardomg.example.spring.security.ws.basic.login.service.DefaultLoginService] for bean with name 'defaultLoginService' defined in file [D:\repositories\spring-ws-basic-security-example\target\classes\com\bernardomg\example\spring\security\ws\basic\login\service\DefaultLoginService.class]: problem with class file or dependent class - at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1555) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:686) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:654) - at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:735) - at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) - at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) - at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) - at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) - at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) - at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) - at com.bernardomg.example.spring.security.ws.basic.Application.main(Application.java:46) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) - at java.base/java.lang.reflect.Method.invoke(Method.java:580) - at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) -Caused by: java.lang.ClassFormatError: Duplicate method name "login" with signature "(Ljava.lang.String;Ljava.lang.String;)LLoginStatus;" in class file com/bernardomg/example/spring/security/ws/basic/login/service/DefaultLoginService - at java.base/java.lang.ClassLoader.defineClass1(Native Method) - at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027) - at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) - at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524) - at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427) - at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421) - at java.base/java.security.AccessController.doPrivileged(AccessController.java:714) - at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420) - at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.findClass(RestartClassLoader.java:136) - at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:118) - at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) - at java.base/java.lang.Class.forName0(Native Method) - at java.base/java.lang.Class.forName(Class.java:534) - at java.base/java.lang.Class.forName(Class.java:513) - at org.springframework.util.ClassUtils.forName(ClassUtils.java:321) - at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:503) - at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1620) - at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1545) - ... 21 more -2024-12-05T23:11:06,632 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:11:06,635 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:11:06,636 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:11:08,076 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:11:08,894 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 2.382 seconds (process running for 351.16) -2024-12-05T23:11:23,627 WARN [Thread-7] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:11:23,727 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:11:23,728 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:11:23,728 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:11:24,921 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:11:25,712 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 2.022 seconds (process running for 367.978) -2024-12-05T23:11:46,550 WARN [Thread-15] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:11:46,658 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:11:46,659 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:11:46,660 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:11:48,178 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:11:48,989 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 2.374 seconds (process running for 391.255) -2024-12-05T23:12:11,921 WARN [Thread-19] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:12:12,022 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:12:12,023 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:12:12,024 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:12:13,409 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:12:14,315 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 2.329 seconds (process running for 416.581) -2024-12-05T23:12:50,682 WARN [Thread-23] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:12:50,790 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:12:50,792 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:12:50,793 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:12:52,213 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:12:52,952 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 2.204 seconds (process running for 455.218) -2024-12-05T23:12:59,601 WARN [Thread-27] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] -2024-12-05T23:12:59,713 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarting(53): Starting Application using Java 21.0.4 with PID 16536 (D:\repositories\spring-ws-basic-security-example\target\classes started by Bernardo in D:\repositories\spring-ws-basic-security-example) -2024-12-05T23:12:59,714 DEBUG [restartedMain] c.b.e.s.s.w.b.Application.logStarting(54): Running with Spring Boot v3.4.0, Spring v6.2.0 -2024-12-05T23:12:59,715 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStartupProfileInfo(652): No active profile set, falling back to 1 default profile: "default" -2024-12-05T23:13:00,904 WARN [restartedMain] o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor(241): spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2024-12-05T23:13:01,663 INFO [restartedMain] c.b.e.s.s.w.b.Application.logStarted(59): Started Application in 1.987 seconds (process running for 463.929) -2024-12-05T23:14:29,184 WARN [SpringApplicationShutdownHook] o.s.b.f.s.DisposableBeanAdapter.destroy(221): Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-232] diff --git a/pom.xml b/pom.xml index fccd37c..80791f3 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,7 @@ + 3.26.3 0.1.5 3.4.0 6.2.0 @@ -135,6 +136,8 @@ ${project.basedir}/src/config/checkstyle/checkstyle-rules.xml + + 3.12.1 @@ -457,6 +460,19 @@ mockito-core test + + + org.mockito + mockito-junit-jupiter + test + + + + org.assertj + assertj-core + ${assertj.version} + test + org.springframework diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/Application.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/Application.java index dd962bf..f336750 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/Application.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/Application.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/AuditConfig.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/AuditConfig.java index dc6accc..07b93dd 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/AuditConfig.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/AuditConfig.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/SecurityConfig.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/SecurityConfig.java index 479a942..ebaa6e2 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/SecurityConfig.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/SecurityConfig.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,9 +31,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.PrivilegeRepository; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.UserRepository; -import com.bernardomg.example.spring.security.ws.basic.security.userdetails.PersistentUserDetailsService; +import com.bernardomg.example.spring.security.ws.basic.springframework.userdetails.UserDomainDetailsService; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; /** * Security configuration. @@ -67,14 +66,11 @@ public PasswordEncoder getPasswordEncoder() { * * @param userRepository * repository for finding users - * @param privilegeRepository - * repository for finding user privileges * @return the user details service */ @Bean("userDetailsService") - public UserDetailsService getUserDetailsService(final UserRepository userRepository, - final PrivilegeRepository privilegeRepository) { - return new PersistentUserDetailsService(userRepository, privilegeRepository); + public UserDetailsService getUserDetailsService(final UserRepository userRepository) { + return new UserDomainDetailsService(userRepository); } } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebConfiguration.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebConfiguration.java index 2f51f16..8099412 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebConfiguration.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebConfiguration.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebSecurityConfig.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebSecurityConfig.java index 45e9af7..c2bc832 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebSecurityConfig.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/WebSecurityConfig.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,8 +24,6 @@ package com.bernardomg.example.spring.security.ws.basic.config; -import java.util.Arrays; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; @@ -34,10 +32,13 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; -import com.bernardomg.example.spring.security.ws.basic.security.configuration.WhitelistRequestCustomizer; +import com.bernardomg.example.spring.security.ws.basic.springframework.web.ErrorResponseAuthenticationEntryPoint; /** * Web security configuration. @@ -61,6 +62,8 @@ public WebSecurityConfig() { * * @param http * HTTP security component + * @param introspector + * utility class to find routes * @param userDetailsService * user details service * @return web security filter chain with all authentication requirements @@ -69,16 +72,31 @@ public WebSecurityConfig() { */ @Bean("webSecurityFilterChain") public SecurityFilterChain getWebSecurityFilterChain(final HttpSecurity http, - final UserDetailsService userDetailsService) throws Exception { + final HandlerMappingIntrospector introspector, final UserDetailsService userDetailsService) + throws Exception { + final MvcRequestMatcher.Builder mvc; + + mvc = new MvcRequestMatcher.Builder(introspector); http // Whitelist access - .authorizeHttpRequests(new WhitelistRequestCustomizer(Arrays.asList("/actuator/**", "/login/**"))) + .authorizeHttpRequests(c -> c + .requestMatchers(mvc.pattern("/actuator/**"), mvc.pattern("/login/**"), mvc.pattern("/favicon.ico"), + mvc.pattern("/error/**")) + .permitAll()) + // Authenticate all others + .authorizeHttpRequests(c -> c.anyRequest() + .authenticated()) + .httpBasic(Customizer.withDefaults()) + // CSRF and CORS .csrf(CsrfConfigurer::disable) - .cors(cors -> {}) + .cors(Customizer.withDefaults()) + // Authentication error handling + .exceptionHandling(handler -> handler.authenticationEntryPoint(new ErrorResponseAuthenticationEntryPoint())) + // Stateless + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // Disable login and logout forms .formLogin(FormLoginConfigurer::disable) - .logout(LogoutConfigurer::disable) - // Activates HTTP Basic authentication - .httpBasic(Customizer.withDefaults()); + .logout(LogoutConfigurer::disable); http.userDetailsService(userDetailsService); diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/package-info.java index bf69e2d..8339718 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/config/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/ExampleEntityController.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/ExampleEntityController.java deleted file mode 100644 index b43a2a4..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/ExampleEntityController.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.controller; - -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.ExampleEntity; -import com.bernardomg.example.spring.security.ws.basic.domain.entity.service.ExampleEntityService; - -import lombok.AllArgsConstructor; -import lombok.NonNull; - -/** - * Rest controller for the example entities. - * - * @author Bernardo Martínez Garrido - */ -@RestController -@RequestMapping("/rest/entity") -@AllArgsConstructor -public class ExampleEntityController { - - /** - * Example entity service. - */ - @NonNull - private final ExampleEntityService exampleEntityService; - - /** - * Creates an entity. - * - * @param entity - * entity to create - * @return the created entity - */ - @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public ExampleEntity create(final ExampleEntity entity) { - return exampleEntityService.create(entity); - } - - /** - * Deletes the entity for the received id. - * - * @param id - * id of the entity to delete - * @return {@code true} if it was deleted, {@code false} otherwise - */ - @DeleteMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public Boolean delete(@PathVariable("id") final Long id) { - return exampleEntityService.delete(id); - } - - /** - * Returns all the entities. - * - * @return all the entities - */ - @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public Iterable read() { - return exampleEntityService.getAll(); - } - - /** - * Updates the entity for the received id. - * - * @param id - * entity id - * @param entity - * new entity data - * @return the updated entity - */ - @PutMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public ExampleEntity update(@PathVariable("id") final Long id, @RequestBody final ExampleEntity entity) { - return exampleEntityService.update(id, entity); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/package-info.java deleted file mode 100644 index 85990d5..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * Model classes. - *

- * These represent the main sets of data which the application works with. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.model; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/package-info.java deleted file mode 100644 index 2a0470d..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -/** - * Repositories. - *

- * Similar to a DAO, a repository is a pattern which allows handling the persistence layer as if it was a collection, - * where entities are stored and read from. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.persistence.repository; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/DefaultExampleEntityService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/DefaultExampleEntityService.java deleted file mode 100644 index 6a925e5..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/DefaultExampleEntityService.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.service; - -import java.util.stream.Collectors; - -import org.springframework.stereotype.Service; - -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.DtoExampleEntity; -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.ExampleEntity; -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.PersistentExampleEntity; -import com.bernardomg.example.spring.security.ws.basic.domain.entity.persistence.repository.ExampleEntityRepository; - -import lombok.AllArgsConstructor; - -/** - * Default implementation of the example entity service. - * - * @author Bernardo Martínez Garrido - */ -@Service -@AllArgsConstructor -public final class DefaultExampleEntityService implements ExampleEntityService { - - /** - * Repository for the domain entities handled by the service. - */ - private final ExampleEntityRepository entityRepository; - - @Override - public final ExampleEntity create(final ExampleEntity data) { - final PersistentExampleEntity entity; - final PersistentExampleEntity saved; - - entity = toEntity(data); - - saved = entityRepository.save(entity); - - return toDto(saved); - } - - @Override - public final Boolean delete(final Long id) { - entityRepository.deleteById(id); - - return true; - } - - @Override - public final Iterable getAll() { - return entityRepository.findAll() - .stream() - .map(this::toDto) - .collect(Collectors.toList()); - } - - @Override - public final ExampleEntity update(final Long id, final ExampleEntity data) { - final PersistentExampleEntity entity; - final PersistentExampleEntity saved; - - entity = toEntity(data); - - saved = entityRepository.save(entity); - - return toDto(saved); - } - - private final ExampleEntity toDto(final PersistentExampleEntity data) { - final DtoExampleEntity dto; - - dto = new DtoExampleEntity(); - dto.setId(data.getId()); - dto.setName(data.getName()); - - return dto; - } - - private final PersistentExampleEntity toEntity(final ExampleEntity data) { - final PersistentExampleEntity entity; - - entity = new PersistentExampleEntity(); - entity.setId(data.getId()); - entity.setName(data.getName()); - - return entity; - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/ExampleEntityService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/ExampleEntityService.java deleted file mode 100644 index 75cc194..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/ExampleEntityService.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.service; - -import org.springframework.security.access.prepost.PreAuthorize; - -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.ExampleEntity; - -/** - * Service for the example entity domain. - *

- * This is a domain service just to allow the endpoints querying the entities they are asked for. - * - * @author Bernardo Martínez Garrido - */ -public interface ExampleEntityService { - - /** - * Creates the received entity. - * - * @param entity - * entity to create - * @return the entity created - */ - @PreAuthorize("hasAuthority('CREATE_DATA')") - public ExampleEntity create(final ExampleEntity entity); - - /** - * Deletes the entity for the received id. - * - * @param id - * entity id - * @return {@code true} if it was deleted, {@code false} otherwise - */ - @PreAuthorize("hasAuthority('DELETE_DATA')") - public Boolean delete(final Long id); - - /** - * Returns all the entities. - * - * @return all the entities - */ - @PreAuthorize("hasAuthority('READ_DATA')") - public Iterable getAll(); - - /** - * Updates the entity for the received id. - * - * @param id - * entity id - * @param entity - * new entity data - * @return the updated entity - */ - @PreAuthorize("hasAuthority('UPDATE_DATA')") - public ExampleEntity update(final Long id, final ExampleEntity entity); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/DefaultLoginService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/DefaultLoginService.java deleted file mode 100644 index 042de60..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/DefaultLoginService.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.login.usecase.service; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Optional; - -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; - -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - * Default implementation of the login service. - * - * @author Bernardo Martínez Garrido - * - */ -@Service -@Slf4j -@AllArgsConstructor -public final class DefaultLoginService implements LoginService { - - /** - * Password encoder, for validating passwords. - */ - private final PasswordEncoder passwordEncoder; - - /** - * User details service, to find and validate users. - */ - private final UserDetailsService userDetailsService; - - @Override - public final LoginStatus login(final String username, final String password) { - final Boolean logged; - final LoginStatus status; - final String token; - Optional details; - - log.debug("Log in attempt for {}", username); - - // Find the user - try { - details = Optional.of(userDetailsService.loadUserByUsername(username)); - } catch (final UsernameNotFoundException e) { - details = Optional.empty(); - } - - // Check if the user is valid - if (details.isEmpty()) { - // No user found for username - log.debug("No user for username {}", username); - logged = false; - } else { - // Validate password - logged = passwordEncoder.matches(password, details.get() - .getPassword()); - if (!logged) { - log.debug("Received password doesn't match the one stored for username {}", username); - } - } - - // Generate token - token = generateToken(username, password); - - status = new LoginStatus(username, logged, token); - - log.debug("Finished log in attempt for {}. Logged in: {}", username, logged); - - return status; - } - - private final String generateToken(final String username, final String password) { - final String rawToken; - - rawToken = String.format("%s:%s", username, password); - return Base64.getEncoder() - .encodeToString(rawToken.getBytes(StandardCharsets.UTF_8)); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/LoginService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/LoginService.java deleted file mode 100644 index 435b0e4..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/LoginService.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.login.usecase.service; - -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; - -/** - * Login service. Takes the user credentials and returns a token. - * - * @author Bernardo Martínez Garrido - * - */ -public interface LoginService { - - /** - * Receives credentials and returns the login status. If it was valid then it contains a token. - * - * @param username - * username to authenticate - * @param password - * password to authenticate - * @return login status - */ - public LoginStatus login(final String username, final String password); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/package-info.java index 308962e..31c804d 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/UserPersonRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/UserPersonRepository.java new file mode 100644 index 0000000..558942d --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/UserPersonRepository.java @@ -0,0 +1,79 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.bernardomg.example.spring.security.ws.basic.person.adapter.inbound.user.repository; + +import java.util.Collection; +import java.util.Objects; + +import org.springframework.stereotype.Repository; + +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; +import com.bernardomg.example.spring.security.ws.basic.person.domain.repository.PersonRepository; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; + +import lombok.extern.slf4j.Slf4j; + +/** + * Person repository which takes the data from the users. Reads from the users repository, and maps into {@code Person}. + * + * @author Bernardo Martínez Garrido + */ +@Slf4j +@Repository +public final class UserPersonRepository implements PersonRepository { + + /** + * User repository. The data for the persons is taken from here. + */ + private final UserRepository userRepository; + + public UserPersonRepository(final UserRepository userRepo) { + super(); + + userRepository = Objects.requireNonNull(userRepo, "Received a null pointer as user repository"); + } + + @Override + public final Collection findAll() { + final Collection persons; + + log.debug("Finding all the persons"); + + persons = userRepository.findAll() + .stream() + .map(this::toPerson) + .toList(); + + log.debug("Found all the persons: {}", persons); + + return persons; + } + + private final Person toPerson(final User user) { + return new Person(user.username(), user.name()); + } + +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/package-info.java new file mode 100644 index 0000000..6db57d2 --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/inbound/user/repository/package-info.java @@ -0,0 +1,29 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * User-based person repository. + */ + +package com.bernardomg.example.spring.security.ws.basic.person.adapter.inbound.user.repository; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/LoginController.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/PersonController.java similarity index 57% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/LoginController.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/PersonController.java index e3388fd..69249f4 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/LoginController.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/PersonController.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,45 +22,37 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.login.adapter.outbound.rest.controller; +package com.bernardomg.example.spring.security.ws.basic.person.adapter.outbound.rest.controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.bernardomg.example.spring.security.ws.basic.login.adapter.outbound.rest.model.UserForm; -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.usecase.service.LoginService; +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; +import com.bernardomg.example.spring.security.ws.basic.person.usecase.service.PersonService; import lombok.AllArgsConstructor; /** - * Login controller. Allows a user to log into the application. + * Person REST controller. * * @author Bernardo Martínez Garrido * */ @RestController -@RequestMapping("/login") +@RequestMapping("/person") @AllArgsConstructor -public class LoginController { +public class PersonController { /** - * Login service. + * Person service. */ - private final LoginService service; + private final PersonService service; - /** - * Logs in a user. - * - * @param user - * user details - * @return the login status after the login attempt - */ - @PostMapping - public LoginStatus login(@RequestBody final UserForm user) { - return service.login(user.username(), user.password()); + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public Iterable readAll() { + return service.getAll(); } } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/package-info.java similarity index 86% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/package-info.java index b40ef11..b3c90ae 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * Login controller model. + * Person controller. */ -package com.bernardomg.example.spring.security.ws.basic.login.adapter.outbound.rest.model; +package com.bernardomg.example.spring.security.ws.basic.person.adapter.outbound.rest.controller; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/UserForm.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/Person.java similarity index 82% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/UserForm.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/Person.java index 5cb6c71..347fda7 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/model/UserForm.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/Person.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,14 +22,13 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.login.adapter.outbound.rest.model; +package com.bernardomg.example.spring.security.ws.basic.person.domain.model; /** - * Contains all the data for a login attempt. + * Person. * * @author Bernardo Martínez Garrido - * */ -public record UserForm(String username, String password) { +public record Person(String id, String name) { } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/package-info.java similarity index 88% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/package-info.java index b1227e4..d486416 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/usecase/service/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * Login services. + * Person model. */ -package com.bernardomg.example.spring.security.ws.basic.login.usecase.service; +package com.bernardomg.example.spring.security.ws.basic.person.domain.model; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.java new file mode 100644 index 0000000..05d87ac --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.java @@ -0,0 +1,45 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.bernardomg.example.spring.security.ws.basic.person.domain.repository; + +import java.util.Collection; + +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; + +/** + * Person repository. + * + * @author Bernardo Martínez Garrido + */ +public interface PersonRepository { + + /** + * Returns all the people. + * + * @return all the people + */ + public Collection findAll(); + +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/package-info.java new file mode 100644 index 0000000..90e2dd6 --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/package-info.java @@ -0,0 +1,29 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Person repository. + */ + +package com.bernardomg.example.spring.security.ws.basic.person.domain.repository; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/ExampleEntityRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/DefaultPersonService.java similarity index 54% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/ExampleEntityRepository.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/DefaultPersonService.java index 2723b87..2b16039 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/ExampleEntityRepository.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/DefaultPersonService.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,32 +22,43 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.domain.entity.persistence.repository; +package com.bernardomg.example.spring.security.ws.basic.person.usecase.service; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Collection; +import java.util.Objects; -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.PersistentExampleEntity; +import org.springframework.stereotype.Service; + +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; +import com.bernardomg.example.spring.security.ws.basic.person.domain.repository.PersonRepository; + +import lombok.extern.slf4j.Slf4j; /** - * Spring-JPA repository for {@link PersistentExampleEntity}. - *

- * This is a simple repository just to allow the endpoints querying the entities they are asked for. + * Default person service, which just takes the data from the person repository. * * @author Bernardo Martínez Garrido */ -public interface ExampleEntityRepository extends JpaRepository { +@Slf4j +@Service +public final class DefaultPersonService implements PersonService { /** - * Returns all entities with a partial match to the name. - * - * @param name - * name for searching - * @param page - * pagination to apply - * @return all entities at least partially matching the name + * Person repository to read the data. */ - public Page findByNameContaining(final String name, final Pageable page); + private final PersonRepository personRepository; + + public DefaultPersonService(final PersonRepository personRepo) { + super(); + + personRepository = Objects.requireNonNull(personRepo); + } + + @Override + public final Collection getAll() { + log.debug("Reading all persons"); + + return personRepository.findAll(); + } } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/DtoExampleEntity.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/PersonService.java similarity index 71% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/DtoExampleEntity.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/PersonService.java index 7c6875a..00de137 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/DtoExampleEntity.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/PersonService.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,21 +22,24 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.domain.entity.model; +package com.bernardomg.example.spring.security.ws.basic.person.usecase.service; -import lombok.Data; +import java.util.Collection; -@Data -public final class DtoExampleEntity implements ExampleEntity { +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; - /** - * Entity id. - */ - private Long id; +/** + * Person service. + * + * @author Bernardo Martínez Garrido + */ +public interface PersonService { /** - * Entity name. + * Returns all the people. + * + * @return all the people */ - private String name; + public Collection getAll(); } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/package-info.java similarity index 87% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/package-info.java index 9e019fc..24308cb 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * Login model. + * Person service. */ -package com.bernardomg.example.spring.security.ws.basic.login.domain.model; +package com.bernardomg.example.spring.security.ws.basic.person.usecase.service; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/configuration/WhitelistRequestCustomizer.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/configuration/WhitelistRequestCustomizer.java deleted file mode 100644 index 1b6ccf8..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/configuration/WhitelistRequestCustomizer.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2024 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.security.configuration; - -import java.util.Collection; -import java.util.Objects; - -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; - -/** - * White list request access configuration. Allows unauthorized access to the routes in the white list. Any other route - * requires authorization. - * - * @author Bernardo Martínez Garrido - * - */ -public final class WhitelistRequestCustomizer implements - Customizer.AuthorizationManagerRequestMatcherRegistry> { - - /** - * White list with the routes which doesn't require authorization. - */ - private final Collection whitelist; - - /** - * Default constructor. - * - * @param list - * white list - */ - public WhitelistRequestCustomizer(final Collection list) { - super(); - - whitelist = Objects.requireNonNull(list); - } - - @Override - public final void customize( - final AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry c) { - c.requestMatchers(whitelist.toArray(new String[whitelist.size()])) - .permitAll() - .anyRequest() - .authenticated(); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/PrivilegeRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/PrivilegeRepository.java deleted file mode 100644 index 70c3d77..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/PrivilegeRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository; - -import java.util.Collection; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentPrivilege; - -/** - * Repository for privileges. - * - * @author Bernardo Martínez Garrido - * - */ -public interface PrivilegeRepository extends JpaRepository { - - /** - * Returns all the privileges for a user. This requires a join from the user up to the privileges. - * - * @param id - * user id - * @return all the privileges for the user - */ - @Query(value = "SELECT p.* FROM privileges p JOIN role_privileges rp ON p.id = rp.privilege_id JOIN roles r ON r.id = rp.role_id JOIN user_roles ur ON r.id = ur.role_id JOIN users u ON u.id = ur.user_id WHERE u.id = :id", - nativeQuery = true) - public Collection findForUser(@Param("id") final Long id); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/PersistentUserDetailsService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/PersistentUserDetailsService.java deleted file mode 100644 index a9ab71f..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/PersistentUserDetailsService.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.security.userdetails; - -import java.util.Collection; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; - -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentPrivilege; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentUser; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.PrivilegeRepository; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.UserRepository; - -import lombok.extern.slf4j.Slf4j; - -/** - * User details service which takes the user data from the persistence layer. - *

- * Makes use of repositories, which will return the user and his privileges. - *

- * The user search is based on the username, and is case insensitive. As the persisted user details are expected to - * contain the username in lower case. - *

Granted authorities

- *

- * Privileges are read moving through the model. The service receives a username and then finds the privileges assigned - * to the related user: - *

- * {@code user -> role -> privileges} - *

- * These privileges are used to create the granted authorities. - *

Exceptions

- *

- * When loading users any of these cases throws a {@code UsernameNotFoundException}: - *

- * - * @author Bernardo Martínez Garrido - * - */ -@Slf4j -public final class PersistentUserDetailsService implements UserDetailsService { - - /** - * Repository for the privileges. - */ - private final PrivilegeRepository privilegeRepo; - - /** - * Repository for the user data. - */ - private final UserRepository userRepo; - - /** - * Constructs a user details service. - * - * @param userRepository - * repository for user details - * @param privilegeRepository - * repository for privileges - */ - public PersistentUserDetailsService(final UserRepository userRepository, - final PrivilegeRepository privilegeRepository) { - super(); - - userRepo = Objects.requireNonNull(userRepository, "Received a null pointer as repository"); - privilegeRepo = Objects.requireNonNull(privilegeRepository, "Received a null pointer as repository"); - } - - @Override - public final UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - final Optional user; - final Collection authorities; - final UserDetails details; - - user = userRepo.findOneByUsername(username.toLowerCase(Locale.getDefault())); - - if (!user.isPresent()) { - log.warn("Username {} not found in DB", username); - throw new UsernameNotFoundException(username); - } - - authorities = getAuthorities(user.get() - .getId()); - - if (authorities.isEmpty()) { - log.warn("Username {} has no authorities", username); - throw new UsernameNotFoundException(username); - } - - details = toUserDetails(user.get(), authorities); - - log.debug("User {} exists", username); - log.debug("Authorities for {}: {}", username, details.getAuthorities()); - log.debug("User flags for {}. Enabled: {}. Non expired: {}. Non locked: {}. Credentials non expired: {}", - username, details.isEnabled(), details.isAccountNonExpired(), details.isAccountNonLocked(), - details.isCredentialsNonExpired()); - - return details; - } - - /** - * Returns all the authorities for the user. - * - * @param id - * id of the user - * @return all the authorities for the user - */ - private final Collection getAuthorities(final Long id) { - return privilegeRepo.findForUser(id) - .stream() - .map(PersistentPrivilege::getName) - .distinct() - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - } - - /** - * Transforms a user entity into a user details object. - * - * @param user - * entity to transform - * @param authorities - * authorities for the user details - * @return equivalent user details - */ - private final UserDetails toUserDetails(final PersistentUser user, - final Collection authorities) { - final Boolean enabled; - final Boolean accountNonExpired; - final Boolean credentialsNonExpired; - final Boolean accountNonLocked; - - // Loads status - enabled = user.getEnabled(); - accountNonExpired = !user.getExpired(); - credentialsNonExpired = !user.getCredentialsExpired(); - accountNonLocked = !user.getLocked(); - - return new User(user.getUsername(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, - accountNonLocked, authorities); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/AuditEventLogger.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/AuditEventLogger.java similarity index 96% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/AuditEventLogger.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/AuditEventLogger.java index b3b342b..76f5ead 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/AuditEventLogger.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/AuditEventLogger.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.security.audit; +package com.bernardomg.example.spring.security.ws.basic.springframework.audit; import java.util.Map; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/package-info.java similarity index 89% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/package-info.java index e45fc71..fe01e7e 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/audit/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/audit/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,4 +26,4 @@ * Audit components. */ -package com.bernardomg.example.spring.security.ws.basic.security.audit; +package com.bernardomg.example.spring.security.ws.basic.springframework.audit; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/UserDomainDetailsService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/UserDomainDetailsService.java new file mode 100644 index 0000000..42b484d --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/UserDomainDetailsService.java @@ -0,0 +1,162 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.bernardomg.example.spring.security.ws.basic.springframework.userdetails; + +import java.util.Collection; +import java.util.Locale; +import java.util.Objects; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.Privilege; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; + +import lombok.extern.slf4j.Slf4j; + +/** + * User details service which takes the user data from the persistence layer. + *

+ * Makes use of repositories, which will return the user and his privileges. + *

+ * The user search is based on the username, and is case insensitive. As the persisted user details are expected to + * contain the username in lower case. + *

Granted authorities

+ *

+ * Privileges are read moving through the model. The service receives a username and then finds the privileges assigned + * to the related user: + *

+ * {@code user -> role -> privileges} + *

+ * These privileges are used to create the granted authorities. + *

Exceptions

+ *

+ * When loading users any of these cases throws a {@code UsernameNotFoundException}: + *

    + *
  • There is no user for the username
  • + *
  • Theres is a user, but he has no privileges
  • + *
+ * + * @author Bernardo Martínez Garrido + * + */ +@Slf4j +public final class UserDomainDetailsService implements UserDetailsService { + + /** + * User repository. + */ + private final UserRepository userRepository; + + /** + * Constructs a user details service. + * + * @param userRepo + * users repository + */ + public UserDomainDetailsService(final UserRepository userRepo) { + super(); + + userRepository = Objects.requireNonNull(userRepo, "Received a null pointer as user repository"); + } + + @Override + public final UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { + final User user; + final UserDetails details; + final String password; + + user = userRepository.findOne(username.toLowerCase(Locale.getDefault())) + .orElseThrow(() -> { + log.error("Username {} not found in database", username); + throw new UsernameNotFoundException(String.format("Username %s not found in database", username)); + }); + + if (user.privileges() + .isEmpty()) { + log.error("Username {} has no authorities", username); + throw new UsernameNotFoundException(String.format("Username %s has no authorities", username)); + } + + password = userRepository.findPassword(username) + .get(); + details = toUserDetails(user, password); + + log.debug("User {} exists. Enabled: {}. Non expired: {}. Non locked: {}. Credentials non expired: {}", username, + details.isEnabled(), details.isAccountNonExpired(), details.isAccountNonLocked(), + details.isCredentialsNonExpired()); + log.debug("Authorities for {}: {}", username, details.getAuthorities()); + + return details; + } + + /** + * Creates a {@link GrantedAuthority} from the {@link Privilege}. + * + * @param privilege + * privilege to transform + * @return {@code GrantedAuthority} from the {@code Privilege} + */ + private final GrantedAuthority toGrantedAuthority(final Privilege privilege) { + return new SimpleGrantedAuthority(privilege.name()); + } + + /** + * Transforms a user into a user details object. + * + * @param user + * user to transform + * @param password + * user password + * @return equivalent user details + */ + private final UserDetails toUserDetails(final User user, final String password) { + final Boolean enabled; + final Boolean accountNonExpired; + final Boolean credentialsNonExpired; + final Boolean accountNonLocked; + final Collection authorities; + + // Loads status + enabled = user.enabled(); + accountNonExpired = !user.expired(); + credentialsNonExpired = !user.passwordExpired(); + accountNonLocked = !user.locked(); + + // Authorities + authorities = user.privileges() + .stream() + .map(this::toGrantedAuthority) + .toList(); + + return new org.springframework.security.core.userdetails.User(user.username(), password, enabled, + accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + } + +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/package-info.java similarity index 88% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/package-info.java index a12ce4f..3ea1383 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/userdetails/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/userdetails/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,4 +26,4 @@ * User details components. */ -package com.bernardomg.example.spring.security.ws.basic.security.userdetails; +package com.bernardomg.example.spring.security.ws.basic.springframework.userdetails; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/ErrorResponseAuthenticationEntryPoint.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/ErrorResponseAuthenticationEntryPoint.java similarity index 95% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/ErrorResponseAuthenticationEntryPoint.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/ErrorResponseAuthenticationEntryPoint.java index 5f0b635..dd6b7b4 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/ErrorResponseAuthenticationEntryPoint.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/ErrorResponseAuthenticationEntryPoint.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.security.entrypoint; +package com.bernardomg.example.spring.security.ws.basic.springframework.web; import java.io.IOException; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/package-info.java similarity index 87% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/package-info.java index 759b285..b51a03e 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * JWT entry points. + * Security entry points. */ -package com.bernardomg.example.spring.security.ws.basic.security.entrypoint; +package com.bernardomg.example.spring.security.ws.basic.springframework.web; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentPrivilege.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/PrivilegeEntity.java similarity index 84% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentPrivilege.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/PrivilegeEntity.java index 5a03643..35ca952 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentPrivilege.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/PrivilegeEntity.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model; import java.io.Serializable; @@ -36,10 +36,7 @@ import lombok.Data; /** - * Persistent privilege data. - *

- * JPA entities shouldn't end mixed up with the domain model. For this reason this class won't extend any generic - * interface, and instead is a JPA POJO. + * Privilege entity. * * @author Bernardo Martínez Garrido * @@ -49,7 +46,7 @@ @Table(name = "privileges") @TableGenerator(name = "seq_privileges_id", table = "sequences", pkColumnName = "sequence", valueColumnName = "count", allocationSize = 1) -public class PersistentPrivilege implements Serializable { +public class PrivilegeEntity implements Serializable { /** * Serialization id. diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/PersistentExampleEntity.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/RoleEntity.java similarity index 54% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/PersistentExampleEntity.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/RoleEntity.java index b9f87e1..511cbba 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/PersistentExampleEntity.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/RoleEntity.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,51 +22,61 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.domain.entity.model; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model; import java.io.Serializable; +import java.util.Collection; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import jakarta.persistence.Transient; +import jakarta.persistence.TableGenerator; import lombok.Data; /** - * Persistent entity for the example application. - *

- * This makes use of JPA annotations for the persistence configuration. + * Role entity. * * @author Bernardo Martínez Garrido + * */ -@Entity(name = "ExampleEntity") -@Table(name = "example_entities") @Data -public class PersistentExampleEntity implements Serializable { +@Entity(name = "Role") +@Table(name = "roles") +@TableGenerator(name = "seq_roles_id", table = "sequences", pkColumnName = "sequence", valueColumnName = "count", + allocationSize = 1) +public class RoleEntity implements Serializable { /** - * Serialization ID. + * Serialization id. */ - @Transient - private static final long serialVersionUID = 1328776989450853491L; + private static final long serialVersionUID = 8513041662486312372L; /** - * Entity's ID. + * Entity id. */ @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.TABLE, generator = "seq_privileges_id") @Column(name = "id", nullable = false, unique = true) - private Long id = -1L; + private Long id; + + /** + * Privilege name. + */ + @Column(name = "name", nullable = false, unique = true, length = 60) + private String name; /** - * Name of the entity. - *

- * This is to have additional data apart from the id, to be used on the tests. + * Privileges. */ - @Column(name = "name", nullable = false, unique = true) - private String name = ""; + @OneToMany + @JoinTable(name = "role_privileges", joinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "privilege_id", referencedColumnName = "id") }) + private Collection privileges; } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentUser.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/UserEntity.java similarity index 70% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentUser.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/UserEntity.java index f22b4d8..e1cc4c4 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/PersistentUser.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/UserEntity.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,25 +22,26 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model; import java.io.Serializable; +import java.util.Collection; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.TableGenerator; import jakarta.persistence.Transient; import lombok.Data; /** - * Persistent user data. - *

- * JPA entities shouldn't end mixed up with the domain model. For this reason this class won't extend any generic - * interface, and instead is a JPA POJO. + * User entity. * * @author Bernardo Martínez Garrido * @@ -50,37 +51,37 @@ @Table(name = "users") @TableGenerator(name = "seq_users_id", table = "sequences", pkColumnName = "sequence", valueColumnName = "count", allocationSize = 1) -public class PersistentUser implements Serializable { +public class UserEntity implements Serializable { /** * Serialization id. */ @Transient - private static final long serialVersionUID = 4807136960800402795L; + private static final long serialVersionUID = 4807136960800402795L; /** * User expired flag. */ @Column(name = "credentials_expired", nullable = false) - private Boolean credentialsExpired; + private Boolean credentialsExpired; /** * User email. */ @Column(name = "email", nullable = false, length = 60) - private String email; + private String email; /** * User enabled flag. */ @Column(name = "enabled", nullable = false) - private Boolean enabled; + private Boolean enabled; /** * User expired flag. */ @Column(name = "expired", nullable = false) - private Boolean expired; + private Boolean expired; /** * Entity id. @@ -88,30 +89,38 @@ public class PersistentUser implements Serializable { @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "seq_users_id") @Column(name = "id", nullable = false, unique = true) - private Long id; + private Long id; /** * User locked flag. */ @Column(name = "locked", nullable = false) - private Boolean locked; + private Boolean locked; /** * User name. */ @Column(name = "name", nullable = false, unique = true, length = 60) - private String name; + private String name; /** * User password. */ @Column(name = "password", nullable = false, length = 60) - private String password; + private String password; + + /** + * Roles. + */ + @OneToMany + @JoinTable(name = "user_roles", joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") }) + private Collection roles; /** * User name. */ @Column(name = "username", nullable = false, unique = true, length = 60) - private String username; + private String username; } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/package-info.java similarity index 87% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/package-info.java index 9fecfb8..af009fe 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/controller/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/model/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * Controller classes. + * JPA user model. */ -package com.bernardomg.example.spring.security.ws.basic.domain.entity.controller; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/JpaUserRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/JpaUserRepository.java new file mode 100644 index 0000000..fb1b237 --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/JpaUserRepository.java @@ -0,0 +1,103 @@ +/** + * The MIT License (MIT) + *

+ * Copyright (c) 2022-2025 the original author or authors. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.repository; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model.PrivilegeEntity; +import com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model.RoleEntity; +import com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model.UserEntity; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.Privilege; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; + +/** + * JPA implementation of the user repository. + * + * @author Bernardo Martínez Garrido + */ +@Repository +@Transactional +public final class JpaUserRepository implements UserRepository { + + private final UserSpringRepository userSpringRepository; + + public JpaUserRepository(final UserSpringRepository userSpringRepo) { + super(); + + userSpringRepository = Objects.requireNonNull(userSpringRepo, "Received a null pointer as user repository"); + } + + @Override + public final Collection findAll() { + return userSpringRepository.findAll() + .stream() + .map(this::toDomain) + .toList(); + } + + @Override + public Optional findOne(final String username) { + return userSpringRepository.findOneByUsername(username) + .map(this::toDomain); + } + + @Override + public Optional findPassword(final String username) { + return userSpringRepository.findOneByUsername(username) + .map(UserEntity::getPassword); + } + + private Privilege toDomain(final PrivilegeEntity entity) { + return new Privilege(entity.getName()); + } + + private User toDomain(final UserEntity entity) { + final Collection privileges; + + privileges = entity.getRoles() + .stream() + .map(RoleEntity::getPrivileges) + .flatMap(Collection::stream) + .map(this::toDomain) + .toList(); + return User.builder() + .withName(entity.getName()) + .withEmail(entity.getEmail()) + .withUsername(entity.getUsername()) + .withEnabled(entity.getEnabled()) + .withExpired(entity.getExpired()) + .withLocked(entity.getLocked()) + .withPasswordExpired(entity.getCredentialsExpired()) + .withPrivileges(privileges) + .build(); + } + +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/UserRepository.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/UserSpringRepository.java similarity index 69% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/UserRepository.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/UserSpringRepository.java index 06f0d70..333633d 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/UserRepository.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/UserSpringRepository.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,13 +22,13 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentUser; +import com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.model.UserEntity; /** * Repository for users. @@ -36,16 +36,7 @@ * @author Bernardo Martínez Garrido * */ -public interface UserRepository extends JpaRepository { - - /** - * Returns the user details for the received email. - * - * @param email - * email to search for - * @return the user details for the received email - */ - public Optional findOneByEmail(final String email); +public interface UserSpringRepository extends JpaRepository { /** * Returns the user details for the received username. @@ -54,6 +45,6 @@ public interface UserRepository extends JpaRepository { * username to search for * @return the user details for the received username */ - public Optional findOneByUsername(final String username); + public Optional findOneByUsername(final String username); } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/package-info.java similarity index 86% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/package-info.java index 3f63cfb..959b6d7 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/adapter/outbound/rest/controller/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ /** - * Login model. + * JPA user repositories. */ -package com.bernardomg.example.spring.security.ws.basic.login.adapter.outbound.rest.controller; +package com.bernardomg.example.spring.security.ws.basic.user.adapter.inbound.jpa.repository; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/LoginStatus.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/Privilege.java similarity index 83% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/LoginStatus.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/Privilege.java index b2857dd..b119ac2 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/domain/model/LoginStatus.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/Privilege.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,14 +22,14 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.login.domain.model; +package com.bernardomg.example.spring.security.ws.basic.user.domain.model; /** - * Status after a login attempt. + * Privilege. * * @author Bernardo Martínez Garrido * */ -public record LoginStatus(String username, Boolean logged, String token) { +public record Privilege(String name) { } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/User.java similarity index 71% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/User.java index 0792da5..f3b2849 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/User.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,11 +22,20 @@ * SOFTWARE. */ +package com.bernardomg.example.spring.security.ws.basic.user.domain.model; + +import java.util.Collection; + +import lombok.Builder; + /** - * Services. - *

- * While in the MVC architecture all the logic seems to be contained inside the controllers, using an additional layer - * of services helps to isolate all the important logic in the application. + * User. + * + * @author Bernardo Martínez Garrido + * */ +@Builder(setterPrefix = "with") +public record User(String email, String username, String name, boolean enabled, boolean expired, boolean locked, + boolean passwordExpired, Collection privileges) { -package com.bernardomg.example.spring.security.ws.basic.domain.entity.service; +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/package-info.java similarity index 88% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/package-info.java index 15e4227..67eaf07 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/model/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,4 +26,4 @@ * User model. */ -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model; +package com.bernardomg.example.spring.security.ws.basic.user.domain.model; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/ExampleEntity.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/UserRepository.java similarity index 58% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/ExampleEntity.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/UserRepository.java index a1fe865..903c167 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/ExampleEntity.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/UserRepository.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,45 +22,43 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.domain.entity.model; +package com.bernardomg.example.spring.security.ws.basic.user.domain.repository; + +import java.util.Collection; +import java.util.Optional; + +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; /** - * A simple entity to be used as an example. + * User repository. * * @author Bernardo Martínez Garrido */ -public interface ExampleEntity { - - /** - * Returns the identifier assigned to this entity. - *

- * If no identifier has been assigned yet, then the value is expected to be {@code null} or lower than zero. - * - * @return the entity's identifier - */ - public Long getId(); +public interface UserRepository { /** - * Returns the name of the entity. + * Returns all the users. * - * @return the entity's name + * @return the user for the received username */ - public String getName(); + public Collection findAll(); /** - * Sets the identifier assigned to this entity. + * Returns the user for the received username. * - * @param identifier - * the identifier for the entity + * @param username + * user to search for + * @return the user for the received username */ - public void setId(final Long identifier); + public Optional findOne(final String username); /** - * Changes the name of the entity. + * Returns the password for the user. * - * @param name - * the name to set on the entity + * @param username + * user to search for the password + * @return the user password */ - public void setName(final String name); + public Optional findPassword(final String username); } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/package-info.java similarity index 88% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/package-info.java index 79a5fe4..ac5d23f 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/user/persistence/repository/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/package-info.java @@ -1,7 +1,7 @@ /** * The MIT License (MIT) *

- * Copyright (c) 2022-2023 the original author or authors. + * Copyright (c) 2022-2025 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,4 +26,4 @@ * User repositories. */ -package com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository; +package com.bernardomg.example.spring.security.ws.basic.user.domain.repository; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e79d2b9..10e5979 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,4 @@ management: endpoints: web: exposure: - include: auditevents - enabled-by-default: false - auditevents: - enabled: true \ No newline at end of file + include: auditevents \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..db60dac --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + ____ _ _ _ _ _ _ _ + | _ \ (_) /\ | | | | | | (_) | | (_) + | |_) | __ _ ___ _ ___ / \ _ _| |_| |__ ___ _ __ | |_ _ ___ __ _| |_ _ ___ _ __ + | _ < / _` / __| |/ __| / /\ \| | | | __| '_ \ / _ \ '_ \| __| |/ __/ _` | __| |/ _ \| '_ \ + | |_) | (_| \__ \ | (__ / ____ \ |_| | |_| | | | __/ | | | |_| | (_| (_| | |_| | (_) | | | | + |____/ \__,_|___/_|\___| /_/ \_\__,_|\__|_| |_|\___|_| |_|\__|_|\___\__,_|\__|_|\___/|_| |_| + +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityCredentialsExpiredUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityCredentialsExpiredUser.java deleted file mode 100644 index 03adc1d..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityCredentialsExpiredUser.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.test.domain.entity.controller.integration; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; - -@MvcIntegrationTest -@DisplayName("Example entity controller - security - credentials expired user") -@Sql({ "/db/queries/user/credentials_expired.sql", "/db/queries/security/default_role.sql" }) -public final class ITExampleEntityControllerSecurityCredentialsExpiredUser { - - @Autowired - private MockMvc mockMvc; - - public ITExampleEntityControllerSecurityCredentialsExpiredUser() { - super(); - } - - @Test - @DisplayName("An authenticated request is not authorized") - public final void testGet_authenticated_notAuthorized() throws Exception { - final ResultActions result; - - result = mockMvc.perform(getRequestAuthorized()); - - // The operation was accepted - result.andExpect(MockMvcResultMatchers.status() - .isUnauthorized()); - } - - private final RequestBuilder getRequestAuthorized() { - return MockMvcRequestBuilders.get("/rest/entity") - .header("Authorization", "Basic YWRtaW46MTIzNA=="); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityDisabledUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityDisabledUser.java deleted file mode 100644 index e47b313..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityDisabledUser.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.test.domain.entity.controller.integration; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; - -@MvcIntegrationTest -@DisplayName("Example entity controller - security - disabled user") -@Sql({ "/db/queries/user/disabled.sql", "/db/queries/security/default_role.sql" }) -public final class ITExampleEntityControllerSecurityDisabledUser { - - @Autowired - private MockMvc mockMvc; - - public ITExampleEntityControllerSecurityDisabledUser() { - super(); - } - - @Test - @DisplayName("An authenticated request is not authorized") - public final void testGet_authenticated_notAuthorized() throws Exception { - final ResultActions result; - - result = mockMvc.perform(getRequestAuthorized()); - - // The operation was accepted - result.andExpect(MockMvcResultMatchers.status() - .isUnauthorized()); - } - - private final RequestBuilder getRequestAuthorized() { - return MockMvcRequestBuilders.get("/rest/entity") - .header("Authorization", "Basic YWRtaW46MTIzNA=="); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityExpiredUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityExpiredUser.java deleted file mode 100644 index ae72403..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityExpiredUser.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.test.domain.entity.controller.integration; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; - -@MvcIntegrationTest -@DisplayName("Example entity controller - security - expired user") -@Sql({ "/db/queries/user/expired.sql", "/db/queries/security/default_role.sql" }) -public final class ITExampleEntityControllerSecurityExpiredUser { - - @Autowired - private MockMvc mockMvc; - - public ITExampleEntityControllerSecurityExpiredUser() { - super(); - } - - @Test - @DisplayName("An authenticated request is not authorized") - public final void testGet_authenticated_notAuthorized() throws Exception { - final ResultActions result; - - result = mockMvc.perform(getRequestAuthorized()); - - // The operation was accepted - result.andExpect(MockMvcResultMatchers.status() - .isUnauthorized()); - } - - private final RequestBuilder getRequestAuthorized() { - return MockMvcRequestBuilders.get("/rest/entity") - .header("Authorization", "Basic YWRtaW46MTIzNA=="); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityLockedUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityLockedUser.java deleted file mode 100644 index b3600f5..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurityLockedUser.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022-2023 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.test.domain.entity.controller.integration; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; - -@MvcIntegrationTest -@DisplayName("Example entity controller - security - locked user") -@Sql({ "/db/queries/user/locked.sql", "/db/queries/security/default_role.sql" }) -public final class ITExampleEntityControllerSecurityLockedUser { - - @Autowired - private MockMvc mockMvc; - - public ITExampleEntityControllerSecurityLockedUser() { - super(); - } - - @Test - @DisplayName("An authenticated request is not authorized") - public final void testGet_authenticated_notAuthorized() throws Exception { - final ResultActions result; - - result = mockMvc.perform(getRequestAuthorized()); - - // The operation was accepted - result.andExpect(MockMvcResultMatchers.status() - .isUnauthorized()); - } - - private final RequestBuilder getRequestAuthorized() { - return MockMvcRequestBuilders.get("/rest/entity") - .header("Authorization", "Basic YWRtaW46MTIzNA=="); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/adapter/outbound/rest/controller/integration/ITLoginControllerSecurity.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/adapter/outbound/rest/controller/integration/ITLoginControllerSecurity.java deleted file mode 100644 index dcd83ea..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/adapter/outbound/rest/controller/integration/ITLoginControllerSecurity.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * The MIT License (MIT) - *

- * Copyright (c) 2022 the original author or authors. - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.bernardomg.example.spring.security.ws.basic.test.login.adapter.outbound.rest.controller.integration; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; - -@MvcIntegrationTest -@DisplayName("Login controller - security") -@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role.sql" }) -public final class ITLoginControllerSecurity { - - @Autowired - private MockMvc mockMvc; - - public ITLoginControllerSecurity() { - super(); - } - - @Test - @DisplayName("Accepts unauthenticated requests") - public final void testGet_unauthorized() throws Exception { - final ResultActions result; - - result = mockMvc.perform(getRequest()); - - // The operation was accepted - result.andExpect(MockMvcResultMatchers.status() - .isOk()); - } - - private final RequestBuilder getRequest() { - return MockMvcRequestBuilders.post("/login") - .contentType(MediaType.APPLICATION_JSON) - .content("{ \"username\":\"admin\", \"password\":\"1234\" }"); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginService.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginService.java deleted file mode 100644 index 206032e..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginService.java +++ /dev/null @@ -1,67 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.usecase.service.unit; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; - -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.usecase.service.LoginService; -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; - -@IntegrationTest -@DisplayName("Login service") -@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role.sql" }) -public class ITLoginService { - - @Autowired - private LoginService service; - - public ITLoginService() { - super(); - } - - @Test - @DisplayName("An existing user with invalid password doesn't log in") - public final void testLogin_invalidPassword() { - final LoginStatus result; - - result = service.login("admin", "abc"); - - Assertions.assertFalse(result.logged()); - } - - @Test - @DisplayName("A not existing user doesn't log in") - public final void testLogin_notExisting() { - final LoginStatus result; - - result = service.login("abc", "1234"); - - Assertions.assertFalse(result.logged()); - } - - @Test - @DisplayName("An existing user with valid password logs in") - public final void testLogin_valid() { - final LoginStatus result; - - result = service.login("admin", "1234"); - - Assertions.assertTrue(result.logged()); - } - - @Test - @DisplayName("A valid login returns all the data") - public final void testLogin_valid_data() { - final LoginStatus result; - - result = service.login("admin", "1234"); - - Assertions.assertEquals("admin", result.username()); - Assertions.assertEquals("YWRtaW46MTIzNA==", result.token()); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoData.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoData.java deleted file mode 100644 index 4212122..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoData.java +++ /dev/null @@ -1,34 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.usecase.service.unit; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.usecase.service.LoginService; -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; - -@IntegrationTest -@DisplayName("Login service - no data") -public class ITLoginServiceNoData { - - @Autowired - private LoginService service; - - public ITLoginServiceNoData() { - super(); - } - - @Test - @DisplayName("Trying to log in returns a user which isn't logged in") - public final void testLogin_invalidPassword() { - final LoginStatus result; - - result = service.login("admin", "abc"); - - Assertions.assertFalse(result.logged()); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoPrivileges.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoPrivileges.java deleted file mode 100644 index 9b16c3f..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/usecase/service/unit/ITLoginServiceNoPrivileges.java +++ /dev/null @@ -1,36 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.usecase.service.unit; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; - -import com.bernardomg.example.spring.security.ws.basic.login.domain.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.usecase.service.LoginService; -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; - -@IntegrationTest -@DisplayName("Login service - no privileges") -@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role_no_privileges.sql" }) -public class ITLoginServiceNoPrivileges { - - @Autowired - private LoginService service; - - public ITLoginServiceNoPrivileges() { - super(); - } - - @Test - @DisplayName("An existing user can't log in") - public final void testLogin_valid() { - final LoginStatus result; - - result = service.login("admin", "1234"); - - Assertions.assertFalse(result.logged()); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurity.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/person/adapter/outbound/rest/controller/integration/ITPersonControllerSecurity.java similarity index 53% rename from src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurity.java rename to src/test/java/com/bernardomg/example/spring/security/ws/basic/test/person/adapter/outbound/rest/controller/integration/ITPersonControllerSecurity.java index f106edb..bbf39cd 100644 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/domain/entity/controller/integration/ITExampleEntityControllerSecurity.java +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/person/adapter/outbound/rest/controller/integration/ITPersonControllerSecurity.java @@ -22,12 +22,11 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.test.domain.entity.controller.integration; +package com.bernardomg.example.spring.security.ws.basic.test.person.adapter.outbound.rest.controller.integration; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.ResultActions; @@ -35,22 +34,33 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.MvcIntegrationTest; +import com.bernardomg.example.spring.security.ws.basic.test.security.user.config.CredentialsExpiredUser; +import com.bernardomg.example.spring.security.ws.basic.test.security.user.config.DisabledUser; +import com.bernardomg.example.spring.security.ws.basic.test.security.user.config.LockedUser; +import com.bernardomg.example.spring.security.ws.basic.test.security.user.config.ValidUser; @MvcIntegrationTest @DisplayName("Example entity controller - security") -@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role.sql" }) -public final class ITExampleEntityControllerSecurity { +final class ITPersonControllerSecurity { + + private static final String ROUTE = "/person"; @Autowired - private MockMvc mockMvc; + private MockMvc mockMvc; - public ITExampleEntityControllerSecurity() { - super(); + private final RequestBuilder getRequest() { + return MockMvcRequestBuilders.get(ROUTE); + } + + private final RequestBuilder getRequestAuthorized() { + return MockMvcRequestBuilders.get(ROUTE) + .header("Authorization", "Basic YWRtaW46MTIzNA=="); } @Test @DisplayName("An authenticated request is authorized") - public final void testGet_authorized() throws Exception { + @ValidUser + void testGet_authorized() throws Exception { final ResultActions result; result = mockMvc.perform(getRequestAuthorized()); @@ -61,24 +71,68 @@ public final void testGet_authorized() throws Exception { } @Test - @DisplayName("A not authenticated request is not authorized") - public final void testGet_unauthorized() throws Exception { + @DisplayName("A locked user is not authorized") + @CredentialsExpiredUser + void testGet_credentialsExpired() throws Exception { final ResultActions result; - result = mockMvc.perform(getRequest()); + result = mockMvc.perform(getRequestAuthorized()); // The operation was accepted result.andExpect(MockMvcResultMatchers.status() .isUnauthorized()); } - private final RequestBuilder getRequest() { - return MockMvcRequestBuilders.get("/rest/entity"); + @Test + @DisplayName("An expired user is not authorized") + @DisabledUser + void testGet_expired() throws Exception { + final ResultActions result; + + result = mockMvc.perform(getRequestAuthorized()); + + // The operation was accepted + result.andExpect(MockMvcResultMatchers.status() + .isUnauthorized()); } - private final RequestBuilder getRequestAuthorized() { - return MockMvcRequestBuilders.get("/rest/entity") - .header("Authorization", "Basic YWRtaW46MTIzNA=="); + @Test + @DisplayName("A locked user is not authorized") + @LockedUser + void testGet_locked() throws Exception { + final ResultActions result; + + result = mockMvc.perform(getRequestAuthorized()); + + // The operation was accepted + result.andExpect(MockMvcResultMatchers.status() + .isUnauthorized()); + } + + @Test + @DisplayName("A disabled user is not authorized") + @DisabledUser + void testGet_notAuthorized() throws Exception { + final ResultActions result; + + result = mockMvc.perform(getRequestAuthorized()); + + // The operation was accepted + result.andExpect(MockMvcResultMatchers.status() + .isUnauthorized()); + } + + @Test + @DisplayName("A not authenticated request is not authorized") + @ValidUser + void testGet_unauthorized() throws Exception { + final ResultActions result; + + result = mockMvc.perform(getRequest()); + + // The operation was accepted + result.andExpect(MockMvcResultMatchers.status() + .isUnauthorized()); } } diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/CredentialsExpiredUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/CredentialsExpiredUser.java new file mode 100644 index 0000000..a06498d --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/CredentialsExpiredUser.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/credentials_expired.sql", "/db/queries/security/default_role.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface CredentialsExpiredUser { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/DisabledUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/DisabledUser.java new file mode 100644 index 0000000..0038488 --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/DisabledUser.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/disabled.sql", "/db/queries/security/default_role.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DisabledUser { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ExpiredUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ExpiredUser.java new file mode 100644 index 0000000..78048e4 --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ExpiredUser.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/expired.sql", "/db/queries/security/default_role.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ExpiredUser { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/LockedUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/LockedUser.java new file mode 100644 index 0000000..4eec58c --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/LockedUser.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/locked.sql", "/db/queries/security/default_role.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface LockedUser { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/UserWithoutPermissions.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/UserWithoutPermissions.java new file mode 100644 index 0000000..f28c449 --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/UserWithoutPermissions.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/single.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface UserWithoutPermissions { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ValidUser.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ValidUser.java new file mode 100644 index 0000000..f221993 --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/config/ValidUser.java @@ -0,0 +1,20 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.security.user.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.test.context.jdbc.Sql; + +@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role.sql" }) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface ValidUser { + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepository.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepository.java deleted file mode 100644 index cec8fe7..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepository.java +++ /dev/null @@ -1,67 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.security.user.persistence.repository.integration; - -import java.util.Collection; -import java.util.stream.Collectors; - -import org.apache.commons.collections4.IterableUtils; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.jdbc.Sql; - -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentPrivilege; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.PrivilegeRepository; -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; - -@IntegrationTest -@DisplayName("Privilege repository") -@Sql({ "/db/queries/user/single.sql", "/db/queries/security/default_role.sql" }) -public class ITPrivilegeRepository { - - @Autowired - private PrivilegeRepository repository; - - public ITPrivilegeRepository() { - super(); - } - - @Test - @DisplayName("Returns all the privileges for a user") - public void testFindForUser_Count() { - final Iterable result; - - result = repository.findForUser(1L); - - Assertions.assertEquals(4, IterableUtils.size(result)); - } - - @Test - @DisplayName("Returns all the data for the privileges of a user") - public void testFindForUser_Data() { - final Collection result; - final Collection privileges; - - result = repository.findForUser(1L); - privileges = result.stream() - .map(PersistentPrivilege::getName) - .collect(Collectors.toList()); - - Assertions.assertTrue(privileges.contains("CREATE_DATA")); - Assertions.assertTrue(privileges.contains("READ_DATA")); - Assertions.assertTrue(privileges.contains("UPDATE_DATA")); - Assertions.assertTrue(privileges.contains("DELETE_DATA")); - } - - @Test - @DisplayName("Returns no privileges for a not existing user") - public void testFindForUser_notExisting() { - final Iterable result; - - result = repository.findForUser(-1L); - - Assertions.assertEquals(0, IterableUtils.size(result)); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepositoryNoData.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepositoryNoData.java deleted file mode 100644 index c76d8c7..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITPrivilegeRepositoryNoData.java +++ /dev/null @@ -1,35 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.security.user.persistence.repository.integration; - -import org.apache.commons.collections4.IterableUtils; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentPrivilege; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.PrivilegeRepository; -import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; - -@IntegrationTest -@DisplayName("Privilege repository - no data") -public class ITPrivilegeRepositoryNoData { - - @Autowired - private PrivilegeRepository repository; - - public ITPrivilegeRepositoryNoData() { - super(); - } - - @Test - @DisplayName("Returns all the privileges for a user") - public void testFindForUser_Count() { - final Iterable result; - - result = repository.findForUser(1L); - - Assertions.assertEquals(0, IterableUtils.size(result)); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/springframework/userdetails/unit/TestUserDomainDetailsService.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/springframework/userdetails/unit/TestUserDomainDetailsService.java new file mode 100644 index 0000000..6c2faa8 --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/springframework/userdetails/unit/TestUserDomainDetailsService.java @@ -0,0 +1,299 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.springframework.userdetails.unit; + +import static org.mockito.BDDMockito.given; + +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import com.bernardomg.example.spring.security.ws.basic.springframework.userdetails.UserDomainDetailsService; +import com.bernardomg.example.spring.security.ws.basic.test.user.config.factory.PermissionConstants; +import com.bernardomg.example.spring.security.ws.basic.test.user.config.factory.UserConstants; +import com.bernardomg.example.spring.security.ws.basic.test.user.config.factory.Users; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; + +@ExtendWith(MockitoExtension.class) +@DisplayName("UserDomainDetailsService") +class TestUserDomainDetailsService { + + @InjectMocks + private UserDomainDetailsService service; + + @Mock + private UserRepository userRepository; + + public TestUserDomainDetailsService() { + super(); + } + + @Test + @DisplayName("When the user is disabled it is returned") + void testLoadByUsername_Disabled() { + final UserDetails userDetails; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.disabled())); + given(userRepository.findPassword(UserConstants.USERNAME)).willReturn(Optional.of(UserConstants.PASSWORD)); + + // WHEN + userDetails = service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(userDetails.getUsername()) + .as("username") + .isEqualTo(UserConstants.USERNAME); + softly.assertThat(userDetails.getPassword()) + .as("password") + .isEqualTo(UserConstants.PASSWORD); + softly.assertThat(userDetails.isAccountNonExpired()) + .as("non expired") + .isTrue(); + softly.assertThat(userDetails.isAccountNonLocked()) + .as("non locked") + .isTrue(); + softly.assertThat(userDetails.isCredentialsNonExpired()) + .as("credentials non expired") + .isTrue(); + softly.assertThat(userDetails.isEnabled()) + .as("enabled") + .isFalse(); + + softly.assertThat(userDetails.getAuthorities()) + .as("authorities size") + .hasSize(1); + softly.assertThat(userDetails.getAuthorities()) + .extracting(GrantedAuthority::getAuthority) + .first() + .as("authority resource") + .isEqualTo(PermissionConstants.DATA_CREATE); + }); + } + + @Test + @DisplayName("When the user is enabled it is returned") + void testLoadByUsername_Enabled() { + final UserDetails userDetails; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.enabled())); + given(userRepository.findPassword(UserConstants.USERNAME)).willReturn(Optional.of(UserConstants.PASSWORD)); + + // WHEN + userDetails = service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(userDetails.getUsername()) + .as("username") + .isEqualTo(UserConstants.USERNAME); + softly.assertThat(userDetails.getPassword()) + .as("password") + .isEqualTo(UserConstants.PASSWORD); + softly.assertThat(userDetails.isAccountNonExpired()) + .as("non expired") + .isTrue(); + softly.assertThat(userDetails.isAccountNonLocked()) + .as("non locked") + .isTrue(); + softly.assertThat(userDetails.isCredentialsNonExpired()) + .as("credentials non expired") + .isTrue(); + softly.assertThat(userDetails.isEnabled()) + .as("enabled") + .isTrue(); + + softly.assertThat(userDetails.getAuthorities()) + .as("authorities size") + .hasSize(1); + softly.assertThat(userDetails.getAuthorities()) + .extracting(GrantedAuthority::getAuthority) + .first() + .as("authority resource") + .isEqualTo(PermissionConstants.DATA_CREATE); + }); + } + + @Test + @DisplayName("When the user is expired it is returned") + void testLoadByUsername_Expired() { + final UserDetails userDetails; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.expired())); + given(userRepository.findPassword(UserConstants.USERNAME)).willReturn(Optional.of(UserConstants.PASSWORD)); + + // WHEN + userDetails = service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(userDetails.getUsername()) + .as("username") + .isEqualTo(UserConstants.USERNAME); + softly.assertThat(userDetails.getPassword()) + .as("password") + .isEqualTo(UserConstants.PASSWORD); + softly.assertThat(userDetails.isAccountNonExpired()) + .as("non expired") + .isFalse(); + softly.assertThat(userDetails.isAccountNonLocked()) + .as("non locked") + .isTrue(); + softly.assertThat(userDetails.isCredentialsNonExpired()) + .as("credentials non expired") + .isTrue(); + softly.assertThat(userDetails.isEnabled()) + .as("enabled") + .isTrue(); + + softly.assertThat(userDetails.getAuthorities()) + .as("authorities size") + .hasSize(1); + softly.assertThat(userDetails.getAuthorities()) + .extracting(GrantedAuthority::getAuthority) + .first() + .as("authority resource") + .isEqualTo(PermissionConstants.DATA_CREATE); + }); + } + + @Test + @DisplayName("When the user is locked it is returned") + void testLoadByUsername_Locked() { + final UserDetails userDetails; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.locked())); + given(userRepository.findPassword(UserConstants.USERNAME)).willReturn(Optional.of(UserConstants.PASSWORD)); + + // WHEN + userDetails = service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(userDetails.getUsername()) + .as("username") + .isEqualTo(UserConstants.USERNAME); + softly.assertThat(userDetails.getPassword()) + .as("password") + .isEqualTo(UserConstants.PASSWORD); + softly.assertThat(userDetails.isAccountNonExpired()) + .as("non expired") + .isTrue(); + softly.assertThat(userDetails.isAccountNonLocked()) + .as("non locked") + .isFalse(); + softly.assertThat(userDetails.isCredentialsNonExpired()) + .as("credentials non expired") + .isTrue(); + softly.assertThat(userDetails.isEnabled()) + .as("enabled") + .isTrue(); + + softly.assertThat(userDetails.getAuthorities()) + .as("authorities size") + .hasSize(1); + softly.assertThat(userDetails.getAuthorities()) + .extracting(GrantedAuthority::getAuthority) + .first() + .as("authority resource") + .isEqualTo(PermissionConstants.DATA_CREATE); + }); + } + + @Test + @DisplayName("When the user doesn't have authorities an exception is thrown") + void testLoadByUsername_NoPrivileges() { + final ThrowingCallable executable; + final Exception exception; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.noPrivileges())); + + // WHEN + executable = () -> service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + exception = Assertions.catchThrowableOfType(UsernameNotFoundException.class, executable); + + Assertions.assertThat(exception.getMessage()) + .isEqualTo("Username " + UserConstants.USERNAME + " has no authorities"); + } + + @Test + @DisplayName("When the user has the password expired it is returned") + void testLoadByUsername_PasswordExpired() { + final UserDetails userDetails; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.of(Users.passwordExpired())); + given(userRepository.findPassword(UserConstants.USERNAME)).willReturn(Optional.of(UserConstants.PASSWORD)); + + // WHEN + userDetails = service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(userDetails.getUsername()) + .as("username") + .isEqualTo(UserConstants.USERNAME); + softly.assertThat(userDetails.getPassword()) + .as("password") + .isEqualTo(UserConstants.PASSWORD); + softly.assertThat(userDetails.isAccountNonExpired()) + .as("non expired") + .isTrue(); + softly.assertThat(userDetails.isAccountNonLocked()) + .as("non locked") + .isTrue(); + softly.assertThat(userDetails.isCredentialsNonExpired()) + .as("credentials non expired") + .isFalse(); + softly.assertThat(userDetails.isEnabled()) + .as("enabled") + .isTrue(); + + softly.assertThat(userDetails.getAuthorities()) + .as("authorities size") + .hasSize(1); + softly.assertThat(userDetails.getAuthorities()) + .extracting(GrantedAuthority::getAuthority) + .first() + .as("authority resource") + .isEqualTo(PermissionConstants.DATA_CREATE); + }); + } + + @Test + @DisplayName("When the user doesn't exist an exception is thrown") + void testLoadByUsername_UserNotExisting() { + final ThrowingCallable executable; + final Exception exception; + + // GIVEN + given(userRepository.findOne(UserConstants.USERNAME)).willReturn(Optional.empty()); + + // WHEN + executable = () -> service.loadUserByUsername(UserConstants.USERNAME); + + // THEN + exception = Assertions.catchThrowableOfType(UsernameNotFoundException.class, executable); + + Assertions.assertThat(exception.getMessage()) + .isEqualTo("Username " + UserConstants.USERNAME + " not found in database"); + } + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/PermissionConstants.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/PermissionConstants.java new file mode 100644 index 0000000..e54988c --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/PermissionConstants.java @@ -0,0 +1,8 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.user.config.factory; + +public final class PermissionConstants { + + public static final String DATA_CREATE = "CREATE:DATA"; + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Privileges.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Privileges.java new file mode 100644 index 0000000..aa11f6f --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Privileges.java @@ -0,0 +1,12 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.user.config.factory; + +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.Privilege; + +public final class Privileges { + + public static final Privilege create() { + return new Privilege(PermissionConstants.DATA_CREATE); + } + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/UserConstants.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/UserConstants.java new file mode 100644 index 0000000..e4daf4b --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/UserConstants.java @@ -0,0 +1,18 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.user.config.factory; + +public final class UserConstants { + + public static final String EMAIL = "mail@somewhere.com"; + + public static final String NAME = "name"; + + public static final String PASSWORD = "1234"; + + public static final String USERNAME = "username"; + + private UserConstants() { + super(); + } + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Users.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Users.java new file mode 100644 index 0000000..807770b --- /dev/null +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/config/factory/Users.java @@ -0,0 +1,88 @@ + +package com.bernardomg.example.spring.security.ws.basic.test.user.config.factory; + +import java.util.List; + +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; + +public final class Users { + + public static final User disabled() { + return User.builder() + .withPrivileges(List.of(Privileges.create())) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(false) + .withExpired(false) + .withPasswordExpired(false) + .withLocked(false) + .build(); + } + + public static final User enabled() { + return User.builder() + .withPrivileges(List.of(Privileges.create())) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(true) + .withExpired(false) + .withPasswordExpired(false) + .withLocked(false) + .build(); + } + + public static final User expired() { + return User.builder() + .withPrivileges(List.of(Privileges.create())) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(true) + .withExpired(true) + .withPasswordExpired(false) + .withLocked(false) + .build(); + } + + public static final User locked() { + return User.builder() + .withPrivileges(List.of(Privileges.create())) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(true) + .withExpired(false) + .withPasswordExpired(false) + .withLocked(true) + .build(); + } + + public static final User noPrivileges() { + return User.builder() + .withPrivileges(List.of()) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(true) + .withExpired(false) + .withPasswordExpired(false) + .withLocked(false) + .build(); + } + + public static final User passwordExpired() { + return User.builder() + .withPrivileges(List.of(Privileges.create())) + .withName(UserConstants.NAME) + .withUsername(UserConstants.USERNAME) + .withEmail(UserConstants.EMAIL) + .withEnabled(true) + .withExpired(false) + .withPasswordExpired(true) + .withLocked(false) + .build(); + } + +} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITUserRepository.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/domain/repository/integration/ITUserRepository.java similarity index 51% rename from src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITUserRepository.java rename to src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/domain/repository/integration/ITUserRepository.java index 7f2439e..5337962 100644 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/security/user/persistence/repository/integration/ITUserRepository.java +++ b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/user/domain/repository/integration/ITUserRepository.java @@ -1,17 +1,17 @@ -package com.bernardomg.example.spring.security.ws.basic.test.security.user.persistence.repository.integration; +package com.bernardomg.example.spring.security.ws.basic.test.user.domain.repository.integration; import java.util.Optional; -import org.junit.jupiter.api.Assertions; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.jdbc.Sql; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.model.PersistentUser; -import com.bernardomg.example.spring.security.ws.basic.security.user.persistence.repository.UserRepository; import com.bernardomg.example.spring.security.ws.basic.test.config.annotation.IntegrationTest; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; +import com.bernardomg.example.spring.security.ws.basic.user.domain.repository.UserRepository; @IntegrationTest @DisplayName("User repository") @@ -28,23 +28,26 @@ public ITUserRepository() { @Test @DisplayName("Returns the user for an existing username") public void testFindForUser_existing() { - final Optional result; + final Optional result; - result = repository.findOneByUsername("admin"); + result = repository.findOne("admin"); - Assertions.assertTrue(result.isPresent()); - Assertions.assertEquals("admin", result.get() - .getUsername()); + Assertions.assertThat(result) + .isPresent(); + Assertions.assertThat("admin") + .isEqualTo(result.get() + .username()); } @Test @DisplayName("Returns no data for a not existing username") public void testFindForUser_notExisting() { - final Optional result; + final Optional result; - result = repository.findOneByUsername("abc"); + result = repository.findOne("abc"); - Assertions.assertFalse(result.isPresent()); + Assertions.assertThat(result) + .isEmpty(); } } diff --git a/src/test/resources/basic_auth.postman_collection b/src/test/resources/basic_auth.postman_collection index 54d5734..323e2f6 100644 --- a/src/test/resources/basic_auth.postman_collection +++ b/src/test/resources/basic_auth.postman_collection @@ -1,288 +1,139 @@ { "info": { - "_postman_id": "b05c67a1-9e73-45f7-9e14-992c9c80d4a2", + "_postman_id": "d28cc57e-16ae-457e-b974-a3c656f32761", "name": "Basic auth", "description": "Requests using the basic HTTP authentication scheme. Which means sending the user and password hashed in the header.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "29322077", + "_collection_link": "https://martian-astronaut-39097.postman.co/workspace/Security-examples~3c194cbc-99d2-481a-a2d3-07aae427b74c/collection/29322077-d28cc57e-16ae-457e-b974-a3c656f32761?action=share&source=collection_link&creator=29322077" }, "item": [ { - "name": "Unauthorized", - "item": [ - { - "name": "Get data", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Unauthorized request\", () => {\r", - " pm.response.to.have.status(401);\r", - "});\r", - "\r", - "pm.test(\"The response contains the error properties\", () => {\r", - " const responseJson = pm.response.json();\r", - " pm.expect(responseJson.errors).to.have.lengthOf(1);\r", - " pm.expect(responseJson.errors[0].message).to.eql('Unauthorized');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "/rest/entity", - "path": [ - "rest", - "entity" - ] - } - }, - "response": [] - } - ], - "description": "Unauthorized requests. These are using no authentication.", + "name": "Get data with authorization", "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "" - ] + "pm.test(\"Authorized request\", () => {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"The response contains the data\", () => {\r", + " const responseJson = pm.response.json();\r", + " pm.expect(responseJson).to.have.property('content');\r", + "});" + ], + "type": "text/javascript", + "packages": {} } } - ] - }, - { - "name": "Wrong authorization scheme", - "item": [ - { - "name": "Get data", - "event": [ + ], + "request": { + "auth": { + "type": "basic", + "basic": [ { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Unauthorized request\", () => {\r", - " pm.response.to.have.status(401);\r", - "});\r", - "\r", - "pm.test(\"The response contains the error properties\", () => {\r", - " const responseJson = pm.response.json();\r", - " pm.expect(responseJson.errors).to.have.lengthOf(1);\r", - " pm.expect(responseJson.errors[0].message).to.eql('Unauthorized');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "/rest/entity", - "path": [ - "rest", - "entity" - ] + "key": "password", + "value": "1234", + "type": "string" + }, + { + "key": "username", + "value": "admin", + "type": "string" } - }, - "response": [] + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "/person", + "path": [ + "person" + ] } - ], - "description": "Requests using the wrong authentication scheme. To make sure the server doesn't support them.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY3NTY2ODkzNCwiZXhwIjoxNjc1Njg2OTM0fQ._oR6pAYlrhpdQq23gTJdGwGncoxAoaAE-GDKdWHQC_TRLcVxdAZDaO02pSfx8JlmAIH2usOnld-NtStjvzNQyw", - "type": "string" - } - ] }, + "response": [] + }, + { + "name": "Get unauthorized data", "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "" - ] + "pm.test(\"Unauthorized request\", () => {\r", + " pm.response.to.have.status(401);\r", + "});\r", + "\r", + "pm.test(\"The response contains the error properties\", () => {\r", + " pm.expect(pm.response.json().message).to.eql('Unauthorized');\r", + "});" + ], + "type": "text/javascript", + "packages": {} } } - ] - }, - { - "name": "Authorized", - "item": [ - { - "name": "Get data", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Authorized request\", () => {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"The response contains the data\", () => {\r", - " const responseJson = pm.response.json();\r", - " pm.expect(responseJson).to.have.property('content');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "/rest/entity", - "path": [ - "rest", - "entity" - ] - } - }, - "response": [] - } ], - "description": "Authorized requests. These check the server handles the authorization scheme.", - "auth": { - "type": "basic", - "basic": [ - { - "key": "username", - "value": "admin", - "type": "string" - }, - { - "key": "password", - "value": "1234", - "type": "string" - } - ] + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "/person", + "path": [ + "person" + ] + } }, + "response": [] + }, + { + "name": "Get data with authorization but no privileges", "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "" - ] + "pm.test(\"Unauthorized request\", () => {\r", + " pm.response.to.have.status(401);\r", + "});\r", + "\r", + "pm.test(\"The response contains the error properties\", () => {\r", + " pm.expect(pm.response.json().message).to.eql('Unauthorized');\r", + "});" + ], + "type": "text/javascript", + "packages": {} } } - ] - }, - { - "name": "Authorized without privileges", - "item": [ - { - "name": "Get data", - "event": [ + ], + "request": { + "auth": { + "type": "basic", + "basic": [ { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Unauthorized request\", () => {\r", - " pm.response.to.have.status(401);\r", - "});\r", - "\r", - "pm.test(\"The response contains the error properties\", () => {\r", - " const responseJson = pm.response.json();\r", - " pm.expect(responseJson.errors).to.have.lengthOf(1);\r", - " pm.expect(responseJson.errors[0].message).to.eql('Unauthorized');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "/entity", - "path": [ - "entity" - ] + "key": "username", + "value": "noroles", + "type": "string" + }, + { + "key": "password", + "value": "1111", + "type": "string" } - }, - "response": [] - } - ], - "description": "Authorized without privileges. In this case these requests cause an authorization error, as the user is authenticated but lacks permissions.", - "auth": { - "type": "basic", - "basic": [ - { - "key": "password", - "value": "1111", - "type": "string" - }, - { - "key": "username", - "value": "noroles", - "type": "string" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } + ] }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } + "method": "GET", + "header": [], + "url": { + "raw": "/person", + "path": [ + "person" + ] } - ] + }, + "response": [] } ], "event": [ diff --git a/src/test/resources/db/queries/security/default_role.sql b/src/test/resources/db/queries/security/default_role.sql index f9edce3..67d8a7f 100644 --- a/src/test/resources/db/queries/security/default_role.sql +++ b/src/test/resources/db/queries/security/default_role.sql @@ -1,9 +1,9 @@ -- All the privileges INSERT INTO privileges (id, name) VALUES - (1, 'CREATE_DATA'), - (2, 'READ_DATA'), - (3, 'UPDATE_DATA'), - (4, 'DELETE_DATA'); + (1, 'CREATE:DATA'), + (2, 'READ:DATA'), + (3, 'UPDATE:DATA'), + (4, 'DELETE:DATA'); -- Default role INSERT INTO roles (id, name) VALUES