diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eadd1fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,43 @@ +# Common +README.md +CHANGELOG.md +docker-compose.yml +Dockerfile + +# Git folder # +/.git/ + +# Compilation folders # +/bin/ +/target/ +/build/ + +# Log files # +/*.log +/*.log.* + +# Eclipse files # +.buildpath +.classpath +.cproject +.externalToolBuilders/ +.launch +.loadpath +.metadata +.project +.settings/ +bin/** +tmp/** +tmp/**/* +*.tmp +*.nak +*.swp +*~.nib +*.pydevproject +local.properties +/src/main/resources/rebel.xml +.springBeans + +# IntelliJ Idea files # +.idea +*.iml diff --git a/.github/workflows/doc_deployment.yml b/.github/workflows/doc_deployment.yml deleted file mode 100644 index 02846d6..0000000 --- a/.github/workflows/doc_deployment.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Doc deployment - -on: - push: - branches: - - master - - develop - -jobs: - deploy_develop_docs: - name: Deploy docs for develop - uses: Bernardo-MG/github-workflow-maven/.github/workflows/deploy_site.yml@v1 - with: - branch: develop - host: docs.bernardomg.com - jdk: 17 - secrets: - url: ${{ secrets.DEPLOY_DOCS_DEVELOP_SITE }} - username: ${{ secrets.DEPLOY_DOCS_DEVELOP_USER }} - password: ${{ secrets.DEPLOY_DOCS_DEVELOP_PASSWORD }} - - deploy_master_docs: - name: Deploy docs for master - uses: Bernardo-MG/github-workflow-maven/.github/workflows/deploy_site.yml@v1 - with: - branch: master - host: docs.bernardomg.com - jdk: 17 - secrets: - url: ${{ secrets.DEPLOY_DOCS_SITE }} - username: ${{ secrets.DEPLOY_DOCS_USER }} - password: ${{ secrets.DEPLOY_DOCS_PASSWORD }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1dd7dd..67d2170 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,24 @@ name: Tests on: [push, pull_request] jobs: - tests_17: - name: Tests with JDK 17 - uses: Bernardo-MG/github-workflow-maven/.github/workflows/testing.yml@v1 - with: - jdk: 17 + tests: + name: Tests with JDK ${{ matrix.jdk }} + runs-on: ubuntu-latest + + strategy: + matrix: + jdk: [17, 21] + + steps: + - name: Check-out + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.jdk }} + distribution: 'temurin' + cache: 'maven' + - name: Run up to integration tests + run: mvn verify -fae diff --git a/.gitignore b/.gitignore index 7aceced..3b01119 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ # Log files # /*.log /*.log.* +/logs/** # Output folders # /test-output/ @@ -48,6 +49,10 @@ local.properties .idea *.iml +# Visual Studio # +.vscode/ +.factorypath + # Windows files # Thumbs.db Desktop.ini 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/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6e238e9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,47 @@ +# ----------------------------------------------------------------------------- +# BUILD STAGE +# ----------------------------------------------------------------------------- +FROM maven:3.9.9-eclipse-temurin-22-alpine as build + +# Create app directory +WORKDIR /app + +# Resolve and cache dependencies +COPY ./pom.xml . +RUN mvn dependency:go-offline + +# Copy and build +COPY ./src ./src +RUN mvn --batch-mode clean package -DskipTests + +# ----------------------------------------------------------------------------- +# DEPLOYMENT STAGE +# ----------------------------------------------------------------------------- +FROM eclipse-temurin:22-jre-alpine as deployment + +WORKDIR /app + +# Exposed ports +EXPOSE 8080 +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --retries=5 --timeout=10s CMD curl --fail --silent localhost:8080/actuator/health | grep UP || exit 1 + +# Create runner user +RUN addgroup -S runners && \ + adduser --disabled-password -S runner -G runners + +# Add logs folder and assign to runner user +RUN mkdir ./logs && \ + chown runner ./logs +VOLUME ./logs + +# Change to runner user +USER runner + +# Copy from build stage +COPY --from=build ./app/target/*.war ./app.war + +# Run with remote debugging +CMD ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000", "-jar", "app.war"] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..3386f49 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3' +services: + basic-db: + image: postgres:15.0-alpine + environment: + PGUSER: 'postgres' + POSTGRES_DB: 'postgres' + POSTGRES_USER: 'postgres' + POSTGRES_PASSWORD: 'password' + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-U postgres"] + interval: 30s + timeout: 10s + retries: 5 + networks: + - db + basic-ws: + build: + context: ../ + dockerfile: ./docker/Dockerfile + ports: + - "8080:8080" + - "8000:8000" + depends_on: + basic-db: + condition: service_healthy + healthcheck: + test: "wget -T5 -qO- http://localhost:8080/actuator/health | grep UP || exit 1" + interval: 2s + timeout: 3s + retries: 5 + start_period: 60s + environment: + # JDBC + - spring.datasource.url=jdbc:postgresql://basic-db:5432/postgres + - spring.datasource.username=postgres + - spring.datasource.password=password + volumes: + - basic-logs:/app/logs + networks: + - db +volumes: + basic-logs: +networks: + db: diff --git a/pom.xml b/pom.xml index 09550a9..0e9dcf3 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ com.bernardomg.maven base-pom - 1.5.3 + 1.5.7 @@ -21,7 +21,7 @@ com.bernardomg.example spring-ws-basic-security-example - 1.2.2 + 1.3.0 war Spring WS Basic Security Example @@ -45,17 +45,17 @@ scm:git:https://github.com/bernardo-mg/spring-ws-basic-security-example.git scm:git:https://github.com/bernardo-mg/spring-ws-basic-security-example.git head - https://www.github.com/bernardo-mg/spring-ws-basic-security-example + https://github.com/bernardo-mg/spring-ws-basic-security-example GitHub - https://www.github.com/bernardo-mg/spring-ws-basic-security-example/issues + https://github.com/bernardo-mg/spring-ws-basic-security-example/issues Github - https://www.github.com/bernardo-mg/spring-ws-basic-security-example/actions + https://github.com/bernardo-mg/spring-ws-basic-security-example/actions @@ -123,9 +123,10 @@ - 3.0.5 - 6.0.8 - 1.10.6 + 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 @@ -156,7 +159,7 @@ import - + org.springframework.boot spring-boot-dependencies ${spring.boot.version} @@ -167,6 +170,15 @@ + + + + + + com.bernardomg.framework.spring + spring-ws-starter + ${bernardomg.framework.ws.version} + @@ -333,14 +345,6 @@ - - - - - com.h2database - h2 - - @@ -374,7 +378,7 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl @@ -385,6 +389,12 @@ org.apache.logging.log4j log4j-jcl + + + commons-logging + commons-logging + + @@ -392,6 +402,14 @@ log4j-web + + + + + org.postgresql + postgresql + + @@ -410,21 +428,6 @@ jackson-annotations - - - - - io.micrometer - micrometer-commons - ${micrometer.version} - - - - io.micrometer - micrometer-observation - ${micrometer.version} - - @@ -452,22 +455,22 @@ test - - org.junit.platform - junit-platform-runner + + org.mockito + mockito-core test - - - - junit - junit - - - + org.mockito - mockito-core + mockito-junit-jupiter + test + + + + org.assertj + assertj-core + ${assertj.version} test @@ -500,6 +503,12 @@ spring-boot-test-autoconfigure test + + + com.h2database + h2 + test + diff --git a/readme.md b/readme.md index 2920fc1..9dc6b18 100644 --- a/readme.md +++ b/readme.md @@ -4,10 +4,10 @@ Example for setting up basic HTTP Security on a web service with Spring Boot. ## Usage -Just run it as any Spring boot application: +Start the Docker image: ``` -mvn spring-boot:run +docker-compose -f docker/docker-compose.yml --project-name spring-ws-basic-security-example up ``` And the web service be available at [http://localhost:8080/](http://localhost:8080/). @@ -28,12 +28,6 @@ To make things easier import `src/test/resources/basic_auth.postman_collection.j | expcreds | 1111 | all | | noread | 1111 | all minus read | -[![Release docs](https://img.shields.io/badge/docs-release-blue.svg)][site-release] -[![Development docs](https://img.shields.io/badge/docs-develop-blue.svg)][site-develop] - -[![Release javadocs](https://img.shields.io/badge/javadocs-release-blue.svg)][javadoc-release] -[![Development javadocs](https://img.shields.io/badge/javadocs-develop-blue.svg)][javadoc-develop] - ## Features - [Spring MVC](https://spring.io/) @@ -42,23 +36,13 @@ To make things easier import `src/test/resources/basic_auth.postman_collection.j ## Documentation -Documentation is always generated for the latest release, kept in the 'master' branch: - -- The [latest release documentation page][site-release]. -- The [latest release Javadoc site][javadoc-release]. - -Documentation is also generated from the latest snapshot, taken from the 'develop' branch: - -- The [the latest snapshot documentation page][site-develop]. -- The [latest snapshot Javadoc site][javadoc-develop]. - -The documentation site is actually a Maven site, and its sources are included in the project. If required it can be generated by using the following Maven command: +The documentation site is actually a Maven site, its sources are included in the project. Can be generated by using the following Maven command: ``` mvn verify site ``` -The verify phase is required, otherwise some of the reports won't be generated. +The verify phase is required, otherwise some of the reports won't be built. ## Collaborate @@ -80,9 +64,5 @@ If you wish to fork or modify the code, visit the [GitHub project page][scm], wh The project has been released under the [MIT License][license]. [issues]: https://github.com/bernardo-mg/spring-ws-basic-security-example/issues -[javadoc-develop]: https://docs.bernardomg.com/development/maven/spring-ws-basic-security-example/apidocs -[javadoc-release]: https://docs.bernardomg.com/maven/spring-ws-basic-security-example/apidocs [license]: https://www.opensource.org/licenses/mit-license.php [scm]: https://github.com/bernardo-mg/spring-ws-basic-security-example -[site-develop]: https://docs.bernardomg.com/development/maven/spring-ws-basic-security-example -[site-release]: https://docs.bernardomg.com/maven/spring-ws-basic-security-example diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 087f681..7c804f9 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -40,5 +40,10 @@ Recovered login. + + + Updated structure. + + \ No newline at end of file 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 3627689..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 @@ -29,14 +29,16 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; +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.entrypoint.ErrorResponseAuthenticationEntryPoint; +import com.bernardomg.example.spring.security.ws.basic.springframework.web.ErrorResponseAuthenticationEntryPoint; /** * Web security configuration. @@ -60,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 @@ -68,63 +72,35 @@ public WebSecurityConfig() { */ @Bean("webSecurityFilterChain") public SecurityFilterChain getWebSecurityFilterChain(final HttpSecurity http, - final UserDetailsService userDetailsService) throws Exception { - final Customizer.AuthorizationManagerRequestMatcherRegistry> authorizeRequestsCustomizer; - final Customizer> formLoginCustomizer; - final Customizer> logoutCustomizer; + final HandlerMappingIntrospector introspector, final UserDetailsService userDetailsService) + throws Exception { + final MvcRequestMatcher.Builder mvc; - // Request authorisations - authorizeRequestsCustomizer = getAuthorizeRequestsCustomizer(); - - // Login form - // Disabled - formLoginCustomizer = c -> c.disable(); - - // Logout form - // Disabled - logoutCustomizer = c -> c.disable(); - - http.csrf() - .disable() - .cors() - .and() - .authorizeHttpRequests(authorizeRequestsCustomizer) - .formLogin(formLoginCustomizer) - .logout(logoutCustomizer) - // Activates HTTP Basic authentication - .httpBasic(); + mvc = new MvcRequestMatcher.Builder(introspector); + http + // Whitelist access + .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(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); http.userDetailsService(userDetailsService); return http.build(); } - /** - * Returns the request authorisation configuration. - * - * @return the request authorisation configuration - */ - private final Customizer.AuthorizationManagerRequestMatcherRegistry> - getAuthorizeRequestsCustomizer() { - return c -> { - try { - c.requestMatchers("/actuator/**", "/login/**") - .permitAll() - .anyRequest() - .authenticated() - // Authentication error handling - .and() - .exceptionHandling() - .authenticationEntryPoint(new ErrorResponseAuthenticationEntryPoint()) - // Stateless - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } catch (final Exception e) { - // TODO Handle exception - throw new RuntimeException(e); - } - }; - } - } 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 a34aa41..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/DtoExampleEntity.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/DtoExampleEntity.java deleted file mode 100644 index 7c6875a..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/DtoExampleEntity.java +++ /dev/null @@ -1,42 +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.model; - -import lombok.Data; - -@Data -public final class DtoExampleEntity implements ExampleEntity { - - /** - * Entity id. - */ - private Long id; - - /** - * Entity name. - */ - private String name; - -} 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/domain/entity/model/ExampleEntity.java deleted file mode 100644 index a1fe865..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/ExampleEntity.java +++ /dev/null @@ -1,66 +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.model; - -/** - * A simple entity to be used as an example. - * - * @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(); - - /** - * Returns the name of the entity. - * - * @return the entity's name - */ - public String getName(); - - /** - * Sets the identifier assigned to this entity. - * - * @param identifier - * the identifier for the entity - */ - public void setId(final Long identifier); - - /** - * Changes the name of the entity. - * - * @param name - * the name to set on the entity - */ - public void setName(final String name); - -} 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/domain/entity/persistence/repository/ExampleEntityRepository.java deleted file mode 100644 index 2723b87..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/persistence/repository/ExampleEntityRepository.java +++ /dev/null @@ -1,53 +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.persistence.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -import com.bernardomg.example.spring.security.ws.basic.domain.entity.model.PersistentExampleEntity; - -/** - * Spring-JPA repository for {@link PersistentExampleEntity}. - *

- * This is a simple repository just to allow the endpoints querying the entities they are asked for. - * - * @author Bernardo Martínez Garrido - */ -public interface ExampleEntityRepository extends JpaRepository { - - /** - * 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 - */ - public Page findByNameContaining(final String name, final Pageable page); - -} 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 63d4042..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/domain/entity/service/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/package-info.java deleted file mode 100644 index 0792da5..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/service/package-info.java +++ /dev/null @@ -1,32 +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. - */ - -/** - * 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. - */ - -package com.bernardomg.example.spring.security.ws.basic.domain.entity.service; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/ImmutableLoginStatus.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/ImmutableLoginStatus.java deleted file mode 100644 index 351cb71..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/ImmutableLoginStatus.java +++ /dev/null @@ -1,62 +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.model; - -import lombok.Data; -import lombok.NonNull; - -/** - * Immutable implementation of {@link LoginStatus}. - * - * @author Bernardo Martínez Garrido - * - */ -@Data -public final class ImmutableLoginStatus implements LoginStatus { - - /** - * Logged in flag. - */ - private final Boolean logged; - - /** - * Authentication token. - */ - private final String token; - - /** - * Logged in user username. - */ - private final String username; - - public ImmutableLoginStatus(@NonNull final String usnm, @NonNull final Boolean lgd, @NonNull final String tkn) { - super(); - - username = usnm; - logged = lgd; - token = tkn; - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/LoginStatus.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/LoginStatus.java deleted file mode 100644 index cff9b3c..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/LoginStatus.java +++ /dev/null @@ -1,56 +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.model; - -/** - * Status after a login attempt. - * - * @author Bernardo Martínez Garrido - * - */ -public interface LoginStatus { - - /** - * Returns if the logging attempt was successful. - * - * @return {@code true} if the login was successful, {@code false} otherwise - */ - public Boolean getLogged(); - - /** - * Returns the security token. - * - * @return the security token - */ - public String getToken(); - - /** - * Returns the username of the user who attempted login. - * - * @return the username - */ - public String getUsername(); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/service/DefaultLoginService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/service/DefaultLoginService.java deleted file mode 100644 index 6e7805f..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/service/DefaultLoginService.java +++ /dev/null @@ -1,112 +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.service; - -import java.nio.charset.Charset; -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.model.ImmutableLoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.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 ImmutableLoginStatus(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(Charset.forName("UTF-8"))); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/service/LoginService.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/service/LoginService.java deleted file mode 100644 index 50358d4..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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.service; - -import com.bernardomg.example.spring.security.ws.basic.login.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/mvc/error/handler/GlobalExceptionHandler.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/handler/GlobalExceptionHandler.java deleted file mode 100644 index 9c90ae3..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/handler/GlobalExceptionHandler.java +++ /dev/null @@ -1,114 +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.mvc.error.handler; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -import com.bernardomg.example.spring.security.ws.basic.mvc.error.model.Error; -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.ErrorResponse; -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.Response; - -import lombok.extern.slf4j.Slf4j; - -/** - * Captures and handles exceptions for all the controllers. - * - * @author Bernardo Martínez Garrido - */ -@ControllerAdvice -@Slf4j -public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - - /** - * Default constructor. - */ - public GlobalExceptionHandler() { - super(); - } - - /** - * Handles authentication and authorisation exceptions. - * - * @param ex - * exception to handle - * @param request - * request - * @return unauthorized response - */ - @ExceptionHandler({ AuthenticationException.class, AccessDeniedException.class }) - public final ResponseEntity handleAuthException(final Exception ex, final WebRequest request) { - log.error(ex.getMessage(), ex); - - return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); - } - - /** - * Handles unmapped exceptions. - * - * @param ex - * exception to handle - * @param request - * request - * @return internal error response - */ - @ExceptionHandler({ RuntimeException.class }) - public final ResponseEntity handleExceptionDefault(final Exception ex, final WebRequest request) { - log.error(ex.getMessage(), ex); - - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Override - protected ResponseEntity handleExceptionInternal(final Exception ex, @Nullable final Object body, - final HttpHeaders headers, final HttpStatusCode statusCode, final WebRequest request) { - final ErrorResponse response; - final String message; - final Error failure; - - log.error(ex.getMessage()); - - if (ex.getMessage() == null) { - message = ""; - } else { - message = ex.getMessage(); - } - - failure = Error.of(message); - response = Response.error(failure); - - return super.handleExceptionInternal(ex, response, headers, statusCode, request); - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/Error.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/Error.java deleted file mode 100644 index 2fe996d..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/Error.java +++ /dev/null @@ -1,53 +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.mvc.error.model; - -/** - * Error object. Containing a message with the failure description. - * - * @author Bernardo Martínez Garrido - * - */ -public interface Error { - - /** - * Builds an error. - * - * @param message - * error message - * @return error with the message - */ - public static Error of(final String message) { - return new ImmutableError(message); - } - - /** - * Returns the error message. - * - * @return the error message. - */ - public String getMessage(); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/package-info.java deleted file mode 100644 index 1984c2f..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/package-info.java +++ /dev/null @@ -1,29 +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. - */ - -/** - * Error model. - */ - -package com.bernardomg.example.spring.security.ws.basic.mvc.error.model; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/ResponseAdvice.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/ResponseAdvice.java deleted file mode 100644 index 7a20293..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/ResponseAdvice.java +++ /dev/null @@ -1,93 +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.mvc.response.controller; - -import org.springframework.core.MethodParameter; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; - -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.ErrorResponse; -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.Response; - -import lombok.extern.slf4j.Slf4j; - -/** - * Advice to wrap all the responses into the response object. - *

- * Unless the response is already an instance of {@link Response}, or the Spring {@link ResponseEntity}, it will be - * wrapped into a {@code Response}. - * - * @author Bernardo Martínez Garrido - * - */ -@ControllerAdvice("com.bernardomg.example.spring.security.ws") -@Slf4j -public class ResponseAdvice implements ResponseBodyAdvice { - - /** - * Default constructor. - */ - public ResponseAdvice() { - super(); - } - - @Override - public Object beforeBodyWrite(final Object body, final MethodParameter returnType, - final MediaType selectedContentType, final Class> selectedConverterType, - final ServerHttpRequest request, final ServerHttpResponse response) { - final Object result; - - log.trace("Received {} as response body", body); - if (body instanceof ResponseEntity) { - // Avoid wrapping Spring responses - result = body; - } else if (body instanceof Response) { - // Avoid wrapping responses - result = body; - } else if (body instanceof ErrorResponse) { - // Avoid wrapping error responses - result = body; - } else if (body == null) { - log.debug("Received null as response body"); - result = Response.empty(); - } else { - result = Response.of(body); - } - - return result; - } - - @Override - public boolean supports(final MethodParameter returnType, - final Class> converterType) { - return true; - } - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/package-info.java deleted file mode 100644 index c2d09ad..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/controller/package-info.java +++ /dev/null @@ -1,29 +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. - */ - -/** - * Response controller advices. - */ - -package com.bernardomg.example.spring.security.ws.basic.mvc.response.controller; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/Response.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/Response.java deleted file mode 100644 index 83385f2..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/Response.java +++ /dev/null @@ -1,95 +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.mvc.response.model; - -import java.util.Arrays; -import java.util.Collection; - -import com.bernardomg.example.spring.security.ws.basic.mvc.error.model.Error; - -/** - * Response to the frontend. - * - * @author Bernardo Martínez Garrido - * - * @param - * response content type - */ -public interface Response { - - /** - * Creates an empty response. - * - * @param - * response content type - * @return an empty response - */ - public static Response empty() { - return new ImmutableResponse<>(); - } - - /** - * Creates an error response. - * - * @param failures - * failures which caused the error - * @return an error response - */ - public static ErrorResponse error(final Collection failures) { - return new ImmutableErrorResponse(failures); - } - - /** - * Creates an error response. - * - * @param failure - * failure which caused the error - * @return an error response - */ - public static ErrorResponse error(final Error failure) { - return new ImmutableErrorResponse(Arrays.asList(failure)); - } - - /** - * Creates a response with the specified content. - * - * @param - * response content type - * @param content - * response content - * @return response with the received content - */ - public static Response of(final T content) { - return new ImmutableResponse<>(content); - } - - /** - * Returns the response content. - * - * @return the response content - */ - public T getContent(); - -} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/package-info.java deleted file mode 100644 index daa5837..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/package-info.java +++ /dev/null @@ -1,29 +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. - */ - -/** - * Response classes. - */ - -package com.bernardomg.example.spring.security.ws.basic.mvc.response.model; 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/controller/LoginController.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/PersonController.java similarity index 59% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/controller/LoginController.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/PersonController.java index 9d588b9..69249f4 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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.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.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.model.UserForm; -import com.bernardomg.example.spring.security.ws.basic.login.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.getUsername(), user.getPassword()); + @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/person/adapter/outbound/rest/controller/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/package-info.java new file mode 100644 index 0000000..b3c90ae --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/adapter/outbound/rest/controller/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 controller. + */ + +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/person/domain/model/Person.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/Person.java new file mode 100644 index 0000000..347fda7 --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/Person.java @@ -0,0 +1,34 @@ +/** + * 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.model; + +/** + * Person. + * + * @author Bernardo Martínez Garrido + */ +public record Person(String id, String name) { + +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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/service/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/model/package-info.java index a47c077..d486416 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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.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/mvc/response/model/ErrorResponse.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.java similarity index 75% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ErrorResponse.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.java index 3939903..05d87ac 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ErrorResponse.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/PersonRepository.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,24 +22,24 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.mvc.response.model; +package com.bernardomg.example.spring.security.ws.basic.person.domain.repository; import java.util.Collection; -import com.bernardomg.example.spring.security.ws.basic.mvc.error.model.Error; +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; /** - * Error response to the frontend. + * Person repository. * * @author Bernardo Martínez Garrido */ -public interface ErrorResponse { +public interface PersonRepository { /** - * Returns all the errors caused by the request. + * Returns all the people. * - * @return request errors + * @return all the people */ - public Collection getErrors(); + public Collection findAll(); } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/controller/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/package-info.java similarity index 87% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/controller/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/repository/package-info.java index 21ae558..90e2dd6 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/controller/package-info.java +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/domain/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. + * Person repository. */ -package com.bernardomg.example.spring.security.ws.basic.login.controller; +package com.bernardomg.example.spring.security.ws.basic.person.domain.repository; diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ImmutableResponse.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/DefaultPersonService.java similarity index 55% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ImmutableResponse.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/DefaultPersonService.java index d11fd63..2b16039 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ImmutableResponse.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,46 +22,43 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.mvc.response.model; +package com.bernardomg.example.spring.security.ws.basic.person.usecase.service; -import lombok.Data; -import lombok.NonNull; +import java.util.Collection; +import java.util.Objects; + +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; /** - * Immutable implementation of the response. + * Default person service, which just takes the data from the person repository. * * @author Bernardo Martínez Garrido - * - * @param - * response content type */ -@Data -public class ImmutableResponse implements Response { +@Slf4j +@Service +public final class DefaultPersonService implements PersonService { /** - * Response content. + * Person repository to read the data. */ - private final T content; + private final PersonRepository personRepository; - /** - * Default constructor. - */ - public ImmutableResponse() { + public DefaultPersonService(final PersonRepository personRepo) { super(); - content = null; + personRepository = Objects.requireNonNull(personRepo); } - /** - * Constructs a response with the specified content. - * - * @param cont - * content - */ - public ImmutableResponse(@NonNull final T cont) { - super(); + @Override + public final Collection getAll() { + log.debug("Reading all persons"); - content = cont; + return personRepository.findAll(); } } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/UserForm.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/PersonService.java similarity index 74% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/UserForm.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/PersonService.java index 1a2186c..00de137 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/model/UserForm.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 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,27 +22,24 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.login.model; +package com.bernardomg.example.spring.security.ws.basic.person.usecase.service; -import lombok.Data; +import java.util.Collection; + +import com.bernardomg.example.spring.security.ws.basic.person.domain.model.Person; /** - * Contains all the data for a login attempt. + * Person service. * * @author Bernardo Martínez Garrido - * */ -@Data -public class UserForm { - - /** - * User password. - */ - private String password; +public interface PersonService { /** - * User username. + * Returns all the people. + * + * @return all the people */ - private String username; + public Collection getAll(); } diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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/model/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/person/usecase/service/package-info.java index 0aaccb8..24308cb 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/login/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.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/entrypoint/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/package-info.java deleted file mode 100644 index 759b285..0000000 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/security/entrypoint/package-info.java +++ /dev/null @@ -1,29 +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. - */ - -/** - * JWT entry points. - */ - -package com.bernardomg.example.spring.security.ws.basic.security.entrypoint; 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}: - *

    - *
  • 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 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 83% 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 621022e..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,17 +22,16 @@ * 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; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import com.bernardomg.example.spring.security.ws.basic.mvc.error.model.Error; -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.ErrorResponse; -import com.bernardomg.example.spring.security.ws.basic.mvc.response.model.Response; +import com.bernardomg.web.response.domain.model.ErrorResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; @@ -60,7 +59,6 @@ public ErrorResponseAuthenticationEntryPoint() { public final void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException { final ErrorResponse resp; - final Error error; final ObjectMapper mapper; log.debug("Authentication failure for path {}: {}", request.getServletPath(), authException.getMessage()); @@ -68,8 +66,7 @@ public final void commence(final HttpServletRequest request, final HttpServletRe response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - error = Error.of("Unauthorized"); - resp = Response.error(error); + resp = new ErrorResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()), "Unauthorized"); mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), resp); diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/handler/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/mvc/error/handler/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/springframework/web/package-info.java index 27954f6..b51a03e 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/handler/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 @@ */ /** - * Error handling. + * Security entry points. */ -package com.bernardomg.example.spring.security.ws.basic.mvc.error.handler; +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/user/adapter/inbound/jpa/repository/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/repository/package-info.java new file mode 100644 index 0000000..959b6d7 --- /dev/null +++ b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/adapter/inbound/jpa/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. + */ + +/** + * JPA user repositories. + */ + +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/domain/entity/model/package-info.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/Privilege.java similarity index 82% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/domain/entity/model/package-info.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/Privilege.java index 85990d5..b119ac2 100644 --- 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/user/domain/model/Privilege.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,10 +22,14 @@ * SOFTWARE. */ +package com.bernardomg.example.spring.security.ws.basic.user.domain.model; + /** - * Model classes. - *

- * These represent the main sets of data which the application works with. + * Privilege. + * + * @author Bernardo Martínez Garrido + * */ +public record Privilege(String name) { -package com.bernardomg.example.spring.security.ws.basic.domain.entity.model; +} diff --git a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/ImmutableError.java b/src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/User.java similarity index 74% rename from src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/ImmutableError.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/model/User.java index ba9ed92..f3b2849 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/error/model/ImmutableError.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,24 +22,20 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.mvc.error.model; +package com.bernardomg.example.spring.security.ws.basic.user.domain.model; -import lombok.Data; -import lombok.NonNull; +import java.util.Collection; + +import lombok.Builder; /** - * Immutable error. + * User. * * @author Bernardo Martínez Garrido * */ -@Data -public class ImmutableError implements Error { - - /** - * Failure message. - */ - @NonNull - private final String message; +@Builder(setterPrefix = "with") +public record User(String email, String username, String name, boolean enabled, boolean expired, boolean locked, + boolean passwordExpired, Collection privileges) { } 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/mvc/response/model/ImmutableErrorResponse.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/mvc/response/model/ImmutableErrorResponse.java rename to src/main/java/com/bernardomg/example/spring/security/ws/basic/user/domain/repository/UserRepository.java index 6ddd7db..903c167 100644 --- a/src/main/java/com/bernardomg/example/spring/security/ws/basic/mvc/response/model/ImmutableErrorResponse.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,39 +22,43 @@ * SOFTWARE. */ -package com.bernardomg.example.spring.security.ws.basic.mvc.response.model; +package com.bernardomg.example.spring.security.ws.basic.user.domain.repository; import java.util.Collection; -import java.util.Collections; +import java.util.Optional; -import com.bernardomg.example.spring.security.ws.basic.mvc.error.model.Error; - -import lombok.Data; -import lombok.NonNull; +import com.bernardomg.example.spring.security.ws.basic.user.domain.model.User; /** - * Immutable implementation of the error response. + * User repository. * * @author Bernardo Martínez Garrido */ -@Data -public class ImmutableErrorResponse implements ErrorResponse { +public interface UserRepository { /** - * Response errors. + * Returns all the users. + * + * @return the user for the received username */ - private final Collection errors; + public Collection findAll(); /** - * Constructs a response with the specified errors. + * Returns the user for the received username. * - * @param errs - * errors + * @param username + * user to search for + * @return the user for the received username */ - public ImmutableErrorResponse(@NonNull final Collection errs) { - super(); + public Optional findOne(final String username); - errors = Collections.unmodifiableCollection(errs); - } + /** + * Returns the password for the user. + * + * @param username + * user to search for the password + * @return the user password + */ + 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.properties b/src/main/resources/application.yml similarity index 52% rename from src/main/resources/application.properties rename to src/main/resources/application.yml index 8664b35..66096e1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.yml @@ -3,14 +3,20 @@ ############################################################################### # Development options -# debug=true +# debug: true -# AOP -spring.aop.proxy-target-class=false +spring: + # AOP + aop: + proxy-target-class: false -# Actuators - -# Audit -management.endpoints.web.exposure.include=auditevents -management.endpoints.enabled-by-default=false -management.endpoint.auditevents.enabled=true +# Actuator +management: + endpoints: + web: + exposure: + include: health,auditevents + endpoint: + health: + show-details: always + sensitive: false \ 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/main/resources/db/data/initial.sql b/src/main/resources/db/changelog/data/initial.sql similarity index 100% rename from src/main/resources/db/data/initial.sql rename to src/main/resources/db/changelog/data/initial.sql diff --git a/src/main/resources/db/data/initial_sequences.sql b/src/main/resources/db/changelog/data/initial_sequences.sql similarity index 100% rename from src/main/resources/db/data/initial_sequences.sql rename to src/main/resources/db/changelog/data/initial_sequences.sql diff --git a/src/main/resources/db/data/initial_users.sql b/src/main/resources/db/changelog/data/initial_users.sql similarity index 100% rename from src/main/resources/db/data/initial_users.sql rename to src/main/resources/db/changelog/data/initial_users.sql diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 1f4e96a..8ec6e62 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,32 +2,36 @@ - - app.log - - - + - - - + + + + + + + + + + - + + - - - + + - 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/integration/controller/ITLoginControllerSecurity.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/controller/ITLoginControllerSecurity.java deleted file mode 100644 index a88b57a..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/controller/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.integration.controller; - -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/integration/service/ITLoginService.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginService.java deleted file mode 100644 index 46be025..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginService.java +++ /dev/null @@ -1,67 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.integration.service; - -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.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.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.getLogged()); - } - - @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.getLogged()); - } - - @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.getLogged()); - } - - @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.getUsername()); - Assertions.assertEquals("YWRtaW46MTIzNA==", result.getToken()); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoData.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoData.java deleted file mode 100644 index 4722588..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoData.java +++ /dev/null @@ -1,34 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.integration.service; - -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.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.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.getLogged()); - } - -} diff --git a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoPrivileges.java b/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoPrivileges.java deleted file mode 100644 index 06efb66..0000000 --- a/src/test/java/com/bernardomg/example/spring/security/ws/basic/test/login/integration/service/ITLoginServiceNoPrivileges.java +++ /dev/null @@ -1,36 +0,0 @@ - -package com.bernardomg.example.spring.security.ws.basic.test.login.integration.service; - -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.model.LoginStatus; -import com.bernardomg.example.spring.security.ws.basic.login.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.getLogged()); - } - -} 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/application-test.properties b/src/test/resources/application-test.yml similarity index 71% rename from src/test/resources/application-test.properties rename to src/test/resources/application-test.yml index 52ec860..154390a 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.yml @@ -2,5 +2,6 @@ # APPLICATION CONFIG # ############################################################################### -# Liquibase -spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master-test.yaml +spring: + liquibase: + change-log: classpath:/db/changelog/db.changelog-master-test.yaml \ No newline at end of file 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/data/initial_sequences.sql b/src/test/resources/db/changelog/data/initial_sequences.sql similarity index 100% rename from src/test/resources/db/data/initial_sequences.sql rename to src/test/resources/db/changelog/data/initial_sequences.sql 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 diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml index d6ffff6..faa3365 100644 --- a/src/test/resources/log4j2-test.xml +++ b/src/test/resources/log4j2-test.xml @@ -1,6 +1,6 @@ - + testing.log @@ -8,27 +8,26 @@ - + - + - + - + - - +