params = new LinkedMultiValueMap<>();
- params.add("grant_type", "password");
- params.add("client_id", UNIT_TEST_CLIENT_ID);
- params.add("client_secret", UNIT_TEST_CLIENT_SECRET);
- params.add("username", username);
- params.add("password", password);
-
- ResultActions result
- = mvc.perform(post("/oauth/token")
- .params(params)
- .with(httpBasic(UNIT_TEST_CLIENT_ID, UNIT_TEST_CLIENT_SECRET))
- .accept("application/json;charset=UTF-8"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json;charset=UTF-8"));
-
- String resultString = result.andReturn().getResponse().getContentAsString();
-
- JacksonJsonParser jsonParser = new JacksonJsonParser();
- return jsonParser.parseMap(resultString).get("access_token").toString();
- }
-
- public static String bearer(String accessToken) {
- return "Bearer " + accessToken;
- }
-}
\ No newline at end of file
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
deleted file mode 100644
index 5cc112c..0000000
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.Users;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-public class StubUserDetailsService implements UserDetailsService {
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- switch (username) {
- case Users.OFFICER_EMAIL:
- return new ApplicationUserDetails(Users.officer());
- case Users.CAPTAIN_EMAIL:
- return new ApplicationUserDetails(Users.captain());
- default:
- throw new UsernameNotFoundException(username);
- }
- }
-}
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
new file mode 100644
index 0000000..3ddeac0
--- /dev/null
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
@@ -0,0 +1,30 @@
+package com.example.copsboot.infrastructure.test;
+
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.restdocs.RestDocumentationExtension;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+//tag::class[]
+@Retention(RetentionPolicy.RUNTIME)
+@CopsbootControllerTest
+@ExtendWith(RestDocumentationExtension.class)
+@AutoConfigureRestDocs
+@ContextConfiguration(classes = CopsbootControllerDocumentationTestConfiguration.class)
+public @interface CopsbootControllerDocumentationTest {
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
+ Class>[] value() default {};
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "controllers") //<6>
+ Class>[] controllers() default {};
+}
+//end::class[]
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
new file mode 100644
index 0000000..02e070e
--- /dev/null
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
@@ -0,0 +1,21 @@
+package com.example.copsboot.infrastructure.test;
+
+import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders;
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
+
+@TestConfiguration
+class CopsbootControllerDocumentationTestConfiguration {
+ @Bean
+ public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
+ return configurer -> configurer.operationPreprocessors()
+ .withRequestDefaults(prettyPrint())
+ .withResponseDefaults(prettyPrint(),
+ modifyHeaders().removeMatching("X.*")
+ .removeMatching("Pragma")
+ .removeMatching("Expires"));
+ }
+ }
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
index c33238a..6696635 100644
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
@@ -1,10 +1,10 @@
package com.example.copsboot.infrastructure.test;
-import com.example.copsboot.infrastructure.SpringProfiles;
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.ContextConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -12,23 +12,12 @@
/**
* Custom annotation for all {@link org.springframework.stereotype.Controller Controller} tests on the project. By using
* this single annotation, everything is configured properly to test a controller:
- *
- * - Import of {@link CopsbootControllerTestConfiguration}
- * test
profile active
- *
- *
- * Example usage:
- *
- * @RunWith(SpringRunner.class)
- * @CopsbootControllerTest(UserController.class)
- * public class UserControllerTest {
- *
*/
//tag::class[]
-@Retention(RetentionPolicy.RUNTIME) //<1>
-@WebMvcTest //<2>
-@ContextConfiguration(classes = CopsbootControllerTestConfiguration.class) //<3>
-@ActiveProfiles(SpringProfiles.TEST) //<4>
+@Retention(RetentionPolicy.RUNTIME) //<.>
+@WebMvcTest //<.>
+@AutoConfigureAddonsWebmvcResourceServerSecurity //<.>
+@Import(WebSecurityConfiguration.class) //<.>
public @interface CopsbootControllerTest {
@AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
deleted file mode 100644
index 7231430..0000000
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.example.copsboot.infrastructure.test;
-
-import com.example.copsboot.infrastructure.security.OAuth2ServerConfiguration;
-import com.example.copsboot.infrastructure.security.SecurityConfiguration;
-import com.example.copsboot.infrastructure.security.StubUserDetailsService;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
-
-@TestConfiguration
-@Import(OAuth2ServerConfiguration.class)
-public class CopsbootControllerTestConfiguration {
- @Bean
- public UserDetailsService userDetailsService() {
- return new StubUserDetailsService();
- }
-
- @Bean
- public SecurityConfiguration securityConfiguration() {
- return new SecurityConfiguration();
- }
-
-}
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/UserRepositoryTest.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
index ad7aa55..b37e583 100644
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
@@ -2,13 +2,11 @@
import com.example.orm.jpa.InMemoryUniqueIdGenerator;
import com.example.orm.jpa.UniqueIdGenerator;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
-import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashSet;
import java.util.Locale;
@@ -17,7 +15,6 @@
import static org.assertj.core.api.Assertions.assertThat;
-@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTest {
@@ -27,50 +24,16 @@ public class UserRepositoryTest {
//tag::testStoreUser[]
@Test
public void testStoreUser() {
- HashSet roles = new HashSet<>();
- roles.add(UserRole.OFFICER);
- User user = repository.save(new User(repository.nextId(), //<1>
- "alex.foley@beverly-hills.com",
- "my-secret-pwd",
- roles));
- assertThat(user).isNotNull(); //<6>
+ User user = repository.save(new User(repository.nextId(),
+ "alex.foley@beverly-hills.com",
+ new AuthServerId(UUID.randomUUID()),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ assertThat(user).isNotNull();
- assertThat(repository.count()).isEqualTo(1L); //<7>
+ assertThat(repository.count()).isEqualTo(1L);
}
//end::testStoreUser[]
- //tag::find-by-email-tests[]
- @Test
- public void testFindByEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail());
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
-
- @Test
- public void testFindByEmailIgnoringCase() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail()
- .toUpperCase(Locale.US));
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
-
- @Test
- public void testFindByEmail_unknownEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase("will.not@find.me");
-
- assertThat(optional).isEmpty();
- }
- //end::find-by-email-tests[]
-
//tag::testconfig[]
@TestConfiguration
static class TestConfig {
@@ -80,4 +43,4 @@ public UniqueIdGenerator generator() {
}
}
//end::testconfig[]
-}
\ No newline at end of file
+}
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/Users.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/Users.java
deleted file mode 100644
index 0020a96..0000000
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/Users.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-
-import java.util.UUID;
-
-public class Users {
- private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
-
- public static final String OFFICER_EMAIL = "officer@example.com";
- public static final String OFFICER_PASSWORD = "officer";
- public static final String CAPTAIN_EMAIL = "captain@example.com";
- public static final String CAPTAIN_PASSWORD = "captain";
-
- private static User OFFICER = User.createOfficer(newRandomId(),
- OFFICER_EMAIL,
- PASSWORD_ENCODER.encode(OFFICER_PASSWORD));
-
- private static User CAPTAIN = User.createCaptain(newRandomId(),
- CAPTAIN_EMAIL,
- PASSWORD_ENCODER.encode(CAPTAIN_PASSWORD));
-
-
- public static UserId newRandomId() {
- return new UserId(UUID.randomUUID());
- }
-
- public static User newRandomOfficer() {
- return newRandomOfficer(newRandomId());
- }
-
- public static User newRandomOfficer(UserId userId) {
- String uniqueId = userId.asString().substring(0, 5);
- return User.createOfficer(userId,
- "user-" + uniqueId +
- "@example.com",
- PASSWORD_ENCODER.encode("user"));
- }
-
- public static User officer() {
- return OFFICER;
- }
-
- public static User captain() {
- return CAPTAIN;
- }
-
- private Users() {
- }
-
- public static User newOfficer(String email, String password) {
- return User.createOfficer(newRandomId(), email, PASSWORD_ENCODER.encode(password));
- }
-}
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
index e0d24b0..c142293 100644
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
@@ -1,133 +1,124 @@
package com.example.copsboot.user.web;
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
+import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
-import org.springframework.restdocs.JUnitRestDocumentation;
-import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.restdocs.RestDocumentationExtension;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.context.WebApplicationContext;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
-import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
-import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders;
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
-import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
+// tag::class-annotations[]
@CopsbootControllerTest(UserRestController.class)
+@ExtendWith(RestDocumentationExtension.class)
+@AutoConfigureRestDocs
public class UserRestControllerDocumentation {
-//end::class-annotations[]
- @Rule
- public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
-
- private MockMvc mvc;
-
+ // end::class-annotations[]
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
private UserService service;
- //tag::setup-method[]
- @Autowired
- private WebApplicationContext context; //<1>
- private RestDocumentationResultHandler resultHandler; //<2>
-
- @Before
- public void setUp() {
- resultHandler = document("{method-name}", //<3>
- preprocessRequest(prettyPrint()), //<4>
- preprocessResponse(prettyPrint(), //<5>
- removeMatchingHeaders("X.*", //<6>
- "Pragma",
- "Expires")));
- mvc = MockMvcBuilders.webAppContextSetup(context) //<7>
- .apply(springSecurity()) //<8>
- .apply(documentationConfiguration(restDocumentation)) //<9>
- .alwaysDo(resultHandler) //<10>
- .build();
- }
- //end::setup-method[]
-
//tag::not-logged-in[]
@Test
public void ownUserDetailsWhenNotLoggedInExample() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized())
+ .andDo(document("own-details-unauthorized"));
}
//end::not-logged-in[]
//tag::officer-details[]
@Test
public void authenticatedOfficerDetailsExample() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andDo(resultHandler.document(
- responseFields(
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ mockMvc.perform(MockMvcRequestBuilders.get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER"))))
+ .andExpect(status().isOk())
+ .andDo(document("own-details",
+ responseFields(
+ fieldWithPath("subject").description("The subject from the JWT token"),
+ subsectionWithPath("claims").description("The claims from the JWT token")
+ )));
}
-
//end::officer-details[]
//tag::create-officer[]
@Test
public void createOfficerExample() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters(); //<1>
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password)); //<2>
-
- mvc.perform(post("/api/users") //<3>
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters))) //<4>
- .andExpect(status().isCreated()) //<5>
- .andDo(resultHandler.document(
- requestFields( //<6>
- fieldWithPath("email")
- .description("The email address of the user to be created."),
- fieldWithPath("password")
- .description("The password for the new user.")
- ),
- responseFields( //<7>
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ UserId userId = new UserId(UUID.randomUUID());
+ when(service.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andDo(document("create-user",
+ requestFields( // <.>
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ ),
+ responseFields( // <.>
+ fieldWithPath("userId")
+ .description("The unique id of the user."),
+ fieldWithPath("email")
+ .description("The email address of the user."),
+ fieldWithPath("authServerId")
+ .description("The id of the user on the authorization server."),
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ )));
}
//end::create-officer[]
+
+ //tag::testconfig[]
+ @TestConfiguration
+ static class TestConfig {
+ @Bean
+ public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
+ return configurer -> configurer.operationPreprocessors()
+ .withRequestDefaults(prettyPrint())
+ .withResponseDefaults(prettyPrint(),
+ modifyHeaders().removeMatching("X.*")
+ .removeMatching("Pragma")
+ .removeMatching("Expires"));
+ }
+ }
+ //end::testconfig[]
}
diff --git a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
index 9014594..2acf875 100644
--- a/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
+++ b/chapter06/05 - Refactoring/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
@@ -1,97 +1,84 @@
package com.example.copsboot.user.web;
import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
+// tag::class-annotations[]
@CopsbootControllerTest(UserRestController.class)
-public class UserRestControllerTest {
-//end::class-annotations[]
- @Autowired
- private MockMvc mvc;
+class UserRestControllerTest {
+ // end::class-annotations[]
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
- private UserService service;
+ private UserService userService; //<.>
@Test
- public void givenNotAuthenticated_whenAskingMyDetails_forbidden() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ void givenUnauthenticatedUser_userInfoEndpointReturnsUnauthorized() throws Exception {
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized());
}
@Test
- public void givenAuthenticatedAsOfficer_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.OFFICER_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"))
- ;
+ void givenAuthenticatedUser_userInfoEndpointReturnsOk() throws Exception {
+ String subject = UUID.randomUUID().toString(); //<.>
+ mockMvc.perform(get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(subject)))) //<.>
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("subject").value(subject)) //<.>
+ .andExpect(jsonPath("claims").isMap());
}
@Test
- public void givenAuthenticatedAsCaptain_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.CAPTAIN_EMAIL, Users.CAPTAIN_PASSWORD);
-
- when(service.getUser(Users.captain().getId())).thenReturn(Optional.of(Users.captain()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.CAPTAIN_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles").value("CAPTAIN"));
+ void givenAuthenticatedOfficer_userIsCreated() throws Exception { //<.>
+ UserId userId = new UserId(UUID.randomUUID());
+ when(userService.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("userId").value(userId.asString()))
+ .andExpect(jsonPath("email").value("wim@example.com"))
+ .andExpect(jsonPath("authServerId").value("eaa8b8a5-a264-48be-98de-d8b4ae2750ac"));
}
@Test
- public void testCreateOfficer() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters();
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password));
-
- mvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters)))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(email))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"));
-
- verify(service).createOfficer(email, password);
+ void givenAuthenticatedUserThatIsNotAnOfficer_forbiddenIsReturned() throws Exception {
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString())))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isForbidden()); // <.>
}
}
diff --git a/chapter06/05 - Refactoring/src/test/resources/application-test.properties b/chapter06/05 - Refactoring/src/test/resources/application-test.properties
deleted file mode 100644
index 78c3fdb..0000000
--- a/chapter06/05 - Refactoring/src/test/resources/application-test.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-copsboot-security.mobile-app-client-id=test-client-id
-copsboot-security.mobile-app-client-secret=test-client-secret
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.jar b/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.properties b/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..2e76e18
--- /dev/null
+++ b/chapter07/01 - postgresql/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/chapter07/01 - postgresql/docker-compose.yaml b/chapter07/01 - postgresql/docker-compose.yaml
new file mode 100644
index 0000000..92cea56
--- /dev/null
+++ b/chapter07/01 - postgresql/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: '3'
+services:
+ db:
+ image: 'postgres:16.0'
+ ports:
+ - 5432:5432
+ environment:
+ POSTGRES_PASSWORD: my-postgres-db-pwd
+ identity:
+ image: 'quay.io/keycloak/keycloak:22.0.1'
+ entrypoint: /opt/keycloak/bin/kc.sh start-dev --import-realm
+ ports:
+ - '8180:8080'
+ environment:
+ KEYCLOAK_LOGLEVEL: 'INFO'
+ KEYCLOAK_ADMIN: 'admin'
+ KEYCLOAK_ADMIN_PASSWORD: 'admin-secret'
+ KC_HOSTNAME: 'localhost'
+ KC_HEALTH_ENABLED: 'true'
+ KC_METRICS_ENABLED: 'true'
diff --git a/chapter07/01 - postgresql/mvnw b/chapter07/01 - postgresql/mvnw
index 5bf251c..66df285 100755
--- a/chapter07/01 - postgresql/mvnw
+++ b/chapter07/01 - postgresql/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
+# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@@ -27,7 +27,6 @@
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -36,6 +35,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@@ -50,7 +53,7 @@ fi
cygwin=false;
darwin=false;
mingw=false
-case "`uname`" in
+case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
@@ -58,9 +61,9 @@ case "`uname`" in
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
- export JAVA_HOME="/Library/Java/Home"
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
@@ -68,69 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ JAVA_HOME=$(java-config --jre-home)
fi
fi
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
-
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
-
- saveddir=`pwd`
-
- M2_HOME=`dirname "$PRG"`/..
-
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
-
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
-
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
-# For Migwn, ensure paths are in UNIX format before anything is touched
+# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@@ -146,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
- JAVACMD="`which java`"
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@@ -160,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
-
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
@@ -181,45 +150,159 @@ find_maven_basedir() {
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
- echo "${basedir}"
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
fi
}
-BASE_DIR=`find_maven_basedir "$(pwd)"`
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-echo $MAVEN_PROJECTBASEDIR
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/chapter07/01 - postgresql/mvnw.cmd b/chapter07/01 - postgresql/mvnw.cmd
index 019bd74..95ba6f5 100644
--- a/chapter07/01 - postgresql/mvnw.cmd
+++ b/chapter07/01 - postgresql/mvnw.cmd
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,15 +18,14 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -35,7 +34,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -44,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
@@ -115,11 +116,72 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@@ -129,15 +191,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-exit /B %ERROR_CODE%
+cmd /C exit /B %ERROR_CODE%
diff --git a/chapter07/01 - postgresql/pom.xml b/chapter07/01 - postgresql/pom.xml
index 9edcbaf..7eb0063 100644
--- a/chapter07/01 - postgresql/pom.xml
+++ b/chapter07/01 - postgresql/pom.xml
@@ -1,220 +1,190 @@
-
- 4.0.0
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.2
+
+
+ com.example
+ copsboot
+ 0.0.1-SNAPSHOT
+ copsboot
+ Demo project for Spring Boot
+
+
+ 17
+ 27.1-jre
+
+
- com.example.copsboot
- copsboot
- 0.0.1-SNAPSHOT
- jar
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+
+ com.c4-soft.springaddons
+ spring-addons-starter-oidc
+ 7.1.9
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
- copsboot
- Demo project for Spring Boot
+
+ com.google.guava
+ guava
+ ${guava.version}
+
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.4.RELEASE
-
-
-
-
-
- UTF-8
- UTF-8
- 1.8
-
-
- 1.5.6
-
-
- 29.0-jre
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+
+ org.flywaydb
+ flyway-core
+
+
-
- 2.0.3.RELEASE
-
-
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.c4-soft.springaddons
+ spring-addons-starter-oidc-test
+ 7.1.9
+ test
+
+
+
+ org.springframework.restdocs
+ spring-restdocs-mockmvc
+ test
+
+
+
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.security.oauth.boot
- spring-security-oauth2-autoconfigure
- 2.1.4.RELEASE
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
- com.google.guava
- guava
- ${guava.version}
-
-
- org.projectlombok
- lombok
-
-
-
- org.postgresql
- postgresql
-
-
-
-
- org.flywaydb
- flyway-core
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
- org.springframework.restdocs
- spring-restdocs-mockmvc
- test
-
-
- com.h2database
- h2
- runtime
-
-
- org.assertj
- assertj-core
- test
-
-
-
-
-
-
-
-
- org.asciidoctor
- asciidoctor-maven-plugin
- ${asciidoctor-maven-plugin.version}
-
-
- org.asciidoctor
- asciidoctorj-pdf
- 1.5.0-alpha.16
-
-
- org.asciidoctor
- asciidoctorj
- 1.5.7
-
-
- org.springframework.restdocs
- spring-restdocs-asciidoctor
- ${spring-restdocs.version}
-
-
- org.jruby
- jruby-complete
- 9.1.17.0
-
-
-
-
- generate-docs
- prepare-package
-
- process-asciidoc
-
-
- html
-
-
-
- generate-docs-pdf
- prepare-package
-
- process-asciidoc
-
-
- pdf
-
-
-
-
- html
- book
-
- ${project.version}
-
-
-
-
-
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ true
+ false
+
+ **/*.java
+
+
+
+
+
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ 2.2.1
+
+
+ generate-docs
+ prepare-package
+
+ process-asciidoc
+
+
+ html
+
+
+
+ generate-docs-pdf
+ prepare-package
+
+ process-asciidoc
+
+
+ pdf
+
+
+
+
+
+ org.springframework.restdocs
+ spring-restdocs-asciidoctor
+ ${spring-restdocs.version}
+
+
+ org.asciidoctor
+ asciidoctorj-pdf
+ 2.3.9
+
+
+
+ book
+
+ ${project.version}
+
+
+
+
+
+
+
+
+
+
+ ci
+
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${maven-surefire-plugin.version}
-
- true
- false
-
- **/*.java
-
-
-
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+
-
-
-
-
- ci
-
-
-
- org.asciidoctor
- asciidoctor-maven-plugin
- ${asciidoctor-maven-plugin.version}
-
-
- generate-docs
- prepare-package
-
- process-asciidoc
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/chapter07/01 - postgresql/src/main/asciidoc/Copsboot REST API Guide.adoc b/chapter07/01 - postgresql/src/docs/asciidoc/Copsboot REST API Guide.adoc
similarity index 91%
rename from chapter07/01 - postgresql/src/main/asciidoc/Copsboot REST API Guide.adoc
rename to chapter07/01 - postgresql/src/docs/asciidoc/Copsboot REST API Guide.adoc
index 255bc8e..b0b91ae 100644
--- a/chapter07/01 - postgresql/src/main/asciidoc/Copsboot REST API Guide.adoc
+++ b/chapter07/01 - postgresql/src/docs/asciidoc/Copsboot REST API Guide.adoc
@@ -11,4 +11,4 @@ The Copsboot project uses a REST API for interfacing with the server.
This documentation covers version {project-version} of the application.
-include::_users.adoc[]
\ No newline at end of file
+include::_users.adoc[]
diff --git a/chapter07/01 - postgresql/src/main/asciidoc/_users.adoc b/chapter07/01 - postgresql/src/docs/asciidoc/_users.adoc
similarity index 56%
rename from chapter07/01 - postgresql/src/main/asciidoc/_users.adoc
rename to chapter07/01 - postgresql/src/docs/asciidoc/_users.adoc
index a033db8..2becf75 100644
--- a/chapter07/01 - postgresql/src/main/asciidoc/_users.adoc
+++ b/chapter07/01 - postgresql/src/docs/asciidoc/_users.adoc
@@ -7,12 +7,12 @@ The API allows to get information on the currently logged on user
via a `GET` on `/api/users/me`. If you are not a logged on user, the
following response will be returned:
-operation::own-user-details-when-not-logged-in-example[snippets='http-request,http-response']
+operation::own-details-unauthorized[snippets='http-request,http-response']
//end::initial-doc[]
If you do log on as a user, you get more information on that user:
-operation::authenticated-officer-details-example[snippets='http-request,http-response,response-fields']
+operation::own-details[snippets='http-request,http-response,response-fields']
//tag::create-user[]
@@ -20,5 +20,5 @@ operation::authenticated-officer-details-example[snippets='http-request,http-res
To create an new user, do a `POST` on `/api/users`:
-operation::create-officer-example[snippets='http-request,request-fields,http-response,response-fields']
-//end::create-user[]
\ No newline at end of file
+operation::create-user[snippets='http-request,request-fields,http-response,response-fields']
+//end::create-user[]
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplication.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplication.java
index f4e3307..7b031d7 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplication.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplication.java
@@ -1,40 +1,13 @@
package com.example.copsboot;
-import com.example.orm.jpa.InMemoryUniqueIdGenerator;
-import com.example.orm.jpa.UniqueIdGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
-import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
-
-import javax.sql.DataSource;
-import java.util.UUID;
@SpringBootApplication
public class CopsbootApplication {
- public static void main(String[] args) {
- SpringApplication.run(CopsbootApplication.class, args);
- }
-
- @Bean
- public UniqueIdGenerator uniqueIdGenerator() {
- return new InMemoryUniqueIdGenerator();
- }
-
- //tag::supporting-beans[]
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
+ public static void main(String[] args) {
+ SpringApplication.run(CopsbootApplication.class, args);
+ }
- @Bean
- public TokenStore tokenStore() {
- return new InMemoryTokenStore();
- }
- //end::supporting-beans[]
}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java
new file mode 100644
index 0000000..cb552d7
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java
@@ -0,0 +1,18 @@
+package com.example.copsboot;
+
+import com.example.orm.jpa.InMemoryUniqueIdGenerator;
+import com.example.orm.jpa.UniqueIdGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.UUID;
+
+@Configuration
+public class CopsbootApplicationConfiguration {
+
+ @Bean
+ public UniqueIdGenerator uniqueIdGenerator() {
+ return new InMemoryUniqueIdGenerator();
+ }
+
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java
deleted file mode 100644
index 74f702f..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.example.copsboot;
-
-import com.example.copsboot.infrastructure.SpringProfiles;
-import com.example.copsboot.user.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.context.annotation.Profile;
-import org.springframework.stereotype.Component;
-
-@Component //<1>
-@Profile(SpringProfiles.DEV) //<2>
-public class DevelopmentDbInitializer implements ApplicationRunner {
-
- private final UserService userService;
-
- @Autowired
- public DevelopmentDbInitializer(UserService userService) { //<3>
- this.userService = userService;
- }
-
- @Override
- public void run(ApplicationArguments applicationArguments) { //<4>
- createTestUsers();
- }
-
- private void createTestUsers() {
- userService.createOfficer("officer@example.com", "officer"); //<5>
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java
deleted file mode 100644
index d541b38..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.example.copsboot.infrastructure.json;
-
-import com.example.orm.jpa.EntityId;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import org.springframework.boot.jackson.JsonComponent;
-
-import java.io.IOException;
-
-@JsonComponent //<1>
-public class EntityIdJsonSerializer extends JsonSerializer { //<2>
-
- @Override
- public void serialize(EntityId entityId, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
- jsonGenerator.writeString(entityId.asString()); //<3>
- }
-
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java
deleted file mode 100644
index 8d02905..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserId;
-import com.example.copsboot.user.UserRole;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-import java.util.Collection;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class ApplicationUserDetails extends org.springframework.security.core.userdetails.User {
-
- private static final String ROLE_PREFIX = "ROLE_";
-
- private final UserId userId;
-
- public ApplicationUserDetails(User user) {
- super(user.getEmail(), user.getPassword(), createAuthorities(user.getRoles()));
- this.userId = user.getId();
- }
-
- public UserId getUserId() {
- return userId;
- }
-
- private static Collection createAuthorities(Set roles) {
- return roles.stream()
- .map(userRole -> new SimpleGrantedAuthority(ROLE_PREFIX + userRole.name()))
- .collect(Collectors.toSet());
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java
deleted file mode 100644
index e8dc16a..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-
-import static java.lang.String.format;
-
-@Service //<1>
-public class ApplicationUserDetailsService implements UserDetailsService {
-
- private final UserRepository userRepository;
-
- @Autowired
- public ApplicationUserDetailsService(UserRepository userRepository) { // <2>
- this.userRepository = userRepository;
- }
-
- @Override
- public UserDetails loadUserByUsername(String username) {
- User user = userRepository.findByEmailIgnoreCase(username) //<3>
- .orElseThrow(() -> new UsernameNotFoundException( //<4>
- String.format("User with email %s could not be found",
- username)));
- return new ApplicationUserDetails(user); //<5>
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java
deleted file mode 100644
index e8ad97c..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
-import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
-import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-
-@Configuration
-public class OAuth2ServerConfiguration {
-
- private static final String RESOURCE_ID = "copsboot-service";
-
- @Configuration
- @EnableResourceServer
- @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
- protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
-
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
- resources.resourceId(RESOURCE_ID);
- }
-
- //tag::configure[]
- @Override
- public void configure(HttpSecurity http) throws Exception {
-
- http.authorizeRequests()
- .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
- .and()
- .antMatcher("/api/**")
- .authorizeRequests()
- .antMatchers(HttpMethod.POST, "/api/users").permitAll() //<1>
- .anyRequest().authenticated();
- }
- //end::configure[]
- }
-
- @Configuration
- @EnableAuthorizationServer
- protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
-
- @Autowired
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private UserDetailsService userDetailsService;
-
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @Autowired
- private TokenStore tokenStore;
-
- @Autowired
- private SecurityConfiguration securityConfiguration;
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security.passwordEncoder(passwordEncoder);
- }
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- clients.inMemory()
- .withClient(securityConfiguration.getMobileAppClientId())
- .authorizedGrantTypes("password", "refresh_token")
- .scopes("mobile_app")
- .resourceIds(RESOURCE_ID)
- .secret(passwordEncoder.encode(securityConfiguration.getMobileAppClientSecret()));
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.tokenStore(tokenStore)
- .authenticationManager(authenticationManager)
- .userDetailsService(userDetailsService);
- }
- }
-
- @Configuration
- public static class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
-
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java
deleted file mode 100644
index c246162..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-@Component //<1>
-@ConfigurationProperties(prefix = "copsboot-security") //<2>
-public class SecurityConfiguration {
- private String mobileAppClientId;
- private String mobileAppClientSecret;
-
- public String getMobileAppClientId() {
- return mobileAppClientId;
- }
-
- public void setMobileAppClientId(String mobileAppClientId) {
- this.mobileAppClientId = mobileAppClientId;
- }
-
- public String getMobileAppClientSecret() {
- return mobileAppClientSecret;
- }
-
- public void setMobileAppClientSecret(String mobileAppClientSecret) {
- this.mobileAppClientSecret = mobileAppClientSecret;
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java
new file mode 100644
index 0000000..9fca2b6
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java
@@ -0,0 +1,19 @@
+package com.example.copsboot.infrastructure.security;
+
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.ResourceServerExpressionInterceptUrlRegistryPostProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+
+@Configuration
+@EnableMethodSecurity //<.>
+public class WebSecurityConfiguration {
+
+ @Bean
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { //<.>
+ return registry -> registry.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
+ .requestMatchers("/api/**").authenticated()
+ .anyRequest().authenticated();
+ }
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerId.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerId.java
new file mode 100644
index 0000000..1705863
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerId.java
@@ -0,0 +1,11 @@
+package com.example.copsboot.user;
+
+import org.springframework.util.Assert;
+
+import java.util.UUID;
+
+public record AuthServerId(UUID value) {
+ public AuthServerId {
+ Assert.notNull(value, "The AuthServerId value should not be null");
+ }
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java
new file mode 100644
index 0000000..f2c86b3
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java
@@ -0,0 +1,19 @@
+package com.example.copsboot.user;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+import java.util.UUID;
+
+@Converter(autoApply = true)
+public class AuthServerIdAttributeConverter implements AttributeConverter {
+ @Override
+ public UUID convertToDatabaseColumn(AuthServerId attribute) {
+ return attribute.value();
+ }
+
+ @Override
+ public AuthServerId convertToEntityAttribute(UUID dbData) {
+ return new AuthServerId(dbData);
+ }
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/CreateUserParameters.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/CreateUserParameters.java
new file mode 100644
index 0000000..2f7b0b2
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/CreateUserParameters.java
@@ -0,0 +1,4 @@
+package com.example.copsboot.user;
+
+public record CreateUserParameters(AuthServerId authServerId, String email, String mobileToken) {
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/User.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/User.java
index 236cd6d..32d02a4 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/User.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/User.java
@@ -1,53 +1,37 @@
package com.example.copsboot.user;
import com.example.orm.jpa.AbstractEntity;
-import com.google.common.collect.Sets;
-
-import javax.persistence.*;
-import javax.validation.constraints.NotNull;
-import java.util.Set;
-
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
@Entity
@Table(name = "copsboot_user")
public class User extends AbstractEntity {
private String email;
- private String password;
-
- @ElementCollection(fetch = FetchType.EAGER)
- @Enumerated(EnumType.STRING)
- @NotNull
- private Set roles;
+ private AuthServerId authServerId; //<.>
+ private String mobileToken; //<.>
protected User() {
}
- public User(UserId id, String email, String password, Set roles) {
+ public User(UserId id, String email, AuthServerId authServerId, String mobileToken) { //<.>
super(id);
this.email = email;
- this.password = password;
- this.roles = roles;
- }
-
- public static User createOfficer(UserId userId, String email, String encodedPassword) {
- return new User(userId, email, encodedPassword, Sets.newHashSet(UserRole.OFFICER));
- }
-
- public static User createCaptain(UserId userId, String email, String encodedPassword) {
- return new User(userId, email, encodedPassword, Sets.newHashSet(UserRole.CAPTAIN));
+ this.authServerId = authServerId;
+ this.mobileToken = mobileToken;
}
public String getEmail() {
return email;
}
- public String getPassword() {
- return password;
+ public AuthServerId getAuthServerId() { //<.>
+ return authServerId;
}
- public Set getRoles() {
- return roles;
+ public String getMobileToken() {
+ return mobileToken;
}
}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserNotFoundException.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserNotFoundException.java
deleted file mode 100644
index 1f65f04..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserNotFoundException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(HttpStatus.NOT_FOUND) //<1>
-public class UserNotFoundException extends RuntimeException {
- public UserNotFoundException(UserId userId) {
- super(String.format("Could not find user with id %s", userId.asString()));
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserRepository.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserRepository.java
index 2359735..43f7e98 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserRepository.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserRepository.java
@@ -3,9 +3,9 @@
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
-import java.util.UUID;
+
//tag::class[]
public interface UserRepository extends CrudRepository, UserRepositoryCustom {
- Optional findByEmailIgnoreCase(String email);
+ Optional findByAuthServerId(AuthServerId authServerId);
}
//end::class[]
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserService.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserService.java
index 9e155a3..61846a5 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserService.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserService.java
@@ -1,9 +1,28 @@
package com.example.copsboot.user;
+import org.springframework.stereotype.Service;
+
import java.util.Optional;
-public interface UserService {
- User createOfficer(String email, String password);
+@Service
+public class UserService {
+ private final UserRepository repository; //<.>
+
+ public UserService(UserRepository repository) {
+ this.repository = repository;
+ }
+
+ public Optional findUserByAuthServerId(AuthServerId authServerId) { //<.>
+ return repository.findByAuthServerId(authServerId);
+ }
- Optional getUser(UserId userId);
+ // tag::createUser[]
+ public User createUser(CreateUserParameters createUserParameters) {
+ UserId userId = repository.nextId();
+ User user = new User(userId, createUserParameters.email(),
+ createUserParameters.authServerId(),
+ createUserParameters.mobileToken());
+ return repository.save(user);
+ }
+ // end::createUser[]
}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserServiceImpl.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserServiceImpl.java
deleted file mode 100644
index 9856e84..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/UserServiceImpl.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Service;
-
-import java.util.Optional;
-
-@Service
-public class UserServiceImpl implements UserService {
- private final UserRepository repository;
- private final PasswordEncoder passwordEncoder;
-
- @Autowired
- public UserServiceImpl(UserRepository repository, PasswordEncoder passwordEncoder) {
- this.repository = repository;
- this.passwordEncoder = passwordEncoder;
- }
-
- @Override
- public User createOfficer(String email, String password) {
- User user = User.createOfficer(repository.nextId(), email, passwordEncoder.encode(password));
- return repository.save(user);
- }
-
- @Override
- public Optional getUser(UserId userId) {
- return repository.findById(userId);
- }
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java
deleted file mode 100644
index 7ab85e9..0000000
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.example.copsboot.user.web;
-
-import lombok.Data;
-import org.hibernate.validator.constraints.Email;
-
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-
-@Data
-public class CreateOfficerParameters {
- @NotNull
- @Email
- private String email;
-
- @NotNull
- @Size(min = 6, max = 1000)
- private String password;
-}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java
new file mode 100644
index 0000000..0d8f0ab
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java
@@ -0,0 +1,16 @@
+package com.example.copsboot.user.web;
+
+import com.example.copsboot.user.AuthServerId;
+import com.example.copsboot.user.CreateUserParameters;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.UUID;
+
+public record CreateUserRequest(String mobileToken) { //<.>
+
+ public CreateUserParameters toParameters(Jwt jwt) {
+ AuthServerId authServerId = new AuthServerId(UUID.fromString(jwt.getSubject())); //<.>
+ String email = jwt.getClaimAsString("email"); //<.>
+ return new CreateUserParameters(authServerId, email, mobileToken);
+ }
+}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserDto.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserDto.java
index 3769d1a..2fac96c 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserDto.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserDto.java
@@ -1,21 +1,14 @@
package com.example.copsboot.user.web;
import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserId;
-import com.example.copsboot.user.UserRole;
-import lombok.Value;
-import java.util.Set;
-
-@Value
-public class UserDto {
- private final UserId id;
- private final String email;
- private final Set roles;
+import java.util.UUID;
+public record UserDto(UUID userId, String email, UUID authServerId, String mobileToken) {
public static UserDto fromUser(User user) {
- return new UserDto(user.getId(),
- user.getEmail(),
- user.getRoles());
+ return new UserDto(user.getId().getId(),
+ user.getEmail(),
+ user.getAuthServerId().value(),
+ user.getMobileToken());
}
}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserRestController.java b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserRestController.java
index c74ccd8..796adc1 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserRestController.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/copsboot/user/web/UserRestController.java
@@ -1,41 +1,52 @@
package com.example.copsboot.user.web;
-import com.example.copsboot.infrastructure.security.ApplicationUserDetails;
+import com.example.copsboot.user.AuthServerId;
+import com.example.copsboot.user.CreateUserParameters;
import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserNotFoundException;
import com.example.copsboot.user.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
-import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
@RestController
@RequestMapping("/api/users")
public class UserRestController {
+ private final UserService userService;
- private final UserService service;
-
- @Autowired
- public UserRestController(UserService service) {
- this.service = service;
+ public UserRestController(UserService userService) {
+ this.userService = userService;
}
- @GetMapping("/me")
- public UserDto currentUser(@AuthenticationPrincipal ApplicationUserDetails userDetails) {
- User user = service.getUser(userDetails.getUserId())
- .orElseThrow(() -> new UserNotFoundException(userDetails.getUserId()));
- return UserDto.fromUser(user);
+ // tag::myself[]
+ @GetMapping("/me") //<.>
+ public Map myself(@AuthenticationPrincipal Jwt jwt) { //<.>
+ Optional userByAuthServerId = userService.findUserByAuthServerId(new AuthServerId(UUID.fromString(jwt.getSubject())));
+
+ Map result = new HashMap<>();
+ userByAuthServerId.ifPresent(user -> result.put("userId", user.getId().asString()));
+ result.put("subject", jwt.getSubject());
+ result.put("claims", jwt.getClaims());
+
+ return result;
}
+ // end::myself[]
- //tag::post[]
- @PostMapping //<1>
- @ResponseStatus(HttpStatus.CREATED) //<2>
- public UserDto createOfficer(@Valid @RequestBody CreateOfficerParameters parameters) { //<3>
- User officer = service.createOfficer(parameters.getEmail(), //<4>
- parameters.getPassword());
- return UserDto.fromUser(officer); //<5>
+ // tag::createUser[]
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ @PreAuthorize("hasRole('OFFICER')")
+ public UserDto createUser(@AuthenticationPrincipal Jwt jwt,
+ @RequestBody CreateUserRequest request) {
+ CreateUserParameters parameters = request.toParameters(jwt);
+ User user = userService.createUser(parameters);
+ return UserDto.fromUser(user);
}
- //end::post[]
+ // end::createUser[]
}
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntity.java b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntity.java
index dfa9f1e..275804e 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntity.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntity.java
@@ -2,8 +2,8 @@
import com.example.util.ArtifactForFramework;
-import javax.persistence.EmbeddedId;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.MappedSuperclass;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntityId.java b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntityId.java
index b9ddc5b..f50c4e4 100755
--- a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntityId.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/AbstractEntityId.java
@@ -2,7 +2,7 @@
import com.example.util.ArtifactForFramework;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Objects;
diff --git a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/Entity.java b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/Entity.java
index a573e0e..3a45231 100644
--- a/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/Entity.java
+++ b/chapter07/01 - postgresql/src/main/java/com/example/orm/jpa/Entity.java
@@ -1,6 +1,5 @@
package com.example.orm.jpa;
-import java.io.Serializable;
/**
* Interface for entity objects.
diff --git a/chapter07/01 - postgresql/src/main/resources/application-dev.properties b/chapter07/01 - postgresql/src/main/resources/application-dev.properties
deleted file mode 100644
index f72b4c7..0000000
--- a/chapter07/01 - postgresql/src/main/resources/application-dev.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-copsboot-security.mobile-app-client-id=copsboot-mobile-client
-copsboot-security.mobile-app-client-secret=ccUyb6vS4S8nxfbKPCrN
-
-spring.flyway.locations=classpath:db/migration/h2
-spring.jpa.hibernate.ddl-auto=create-drop
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/src/main/resources/application-local.properties b/chapter07/01 - postgresql/src/main/resources/application-local.properties
index c14a8c4..8fbe161 100644
--- a/chapter07/01 - postgresql/src/main/resources/application-local.properties
+++ b/chapter07/01 - postgresql/src/main/resources/application-local.properties
@@ -3,14 +3,9 @@ spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=my-postgres-db-pwd
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
-spring.jpa.hibernate.ddl-auto=none
+spring.jpa.hibernate.ddl-auto=validate
-copsboot-security.mobile-app-client-id=copsboot-mobile-client
-copsboot-security.mobile-app-client-secret=ccUyb6vS4S8nxfbKPCrN
-
-spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
-spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
-spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.sql
-
-
-spring.flyway.locations=classpath:db/migration/postgresql
\ No newline at end of file
+spring.jpa.properties.jakarta.persistence.schema-generation.create-source=metadata
+spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=create
+spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-target=create.sql
+spring.jpa.properties.hibernate.hbm2ddl.delimiter=;
diff --git a/chapter07/01 - postgresql/src/main/resources/application.properties b/chapter07/01 - postgresql/src/main/resources/application.properties
index e69de29..22c3363 100644
--- a/chapter07/01 - postgresql/src/main/resources/application.properties
+++ b/chapter07/01 - postgresql/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+com.c4-soft.springaddons.oidc.ops[0].iss=http://localhost:8180/realms/copsboot
+com.c4-soft.springaddons.oidc.ops[0].authorities[0].path=$.realm_access.roles
+com.c4-soft.springaddons.oidc.ops[0].authorities[0].prefix=ROLE_
diff --git a/chapter07/01 - postgresql/src/main/resources/db/migration/V1.0.0.1__users.sql b/chapter07/01 - postgresql/src/main/resources/db/migration/V1.0.0.1__users.sql
new file mode 100644
index 0000000..d1939fa
--- /dev/null
+++ b/chapter07/01 - postgresql/src/main/resources/db/migration/V1.0.0.1__users.sql
@@ -0,0 +1,7 @@
+CREATE TABLE copsboot_user
+(
+ id uuid NOT NULL PRIMARY KEY,
+ auth_server_id uuid,
+ email VARCHAR(255),
+ mobile_token VARCHAR(255)
+);
diff --git a/chapter07/01 - postgresql/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql b/chapter07/01 - postgresql/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql
deleted file mode 100644
index 485336f..0000000
--- a/chapter07/01 - postgresql/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql
+++ /dev/null
@@ -1,42 +0,0 @@
-CREATE TABLE oauth_client_details (
- client_id VARCHAR(255) PRIMARY KEY,
- resource_ids VARCHAR(255),
- client_secret VARCHAR(255),
- scope VARCHAR(255),
- authorized_grant_types VARCHAR(255),
- web_server_redirect_uri VARCHAR(255),
- authorities VARCHAR(255),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additional_information VARCHAR(4096),
- autoapprove VARCHAR(255)
-);
-
-CREATE TABLE oauth_client_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication_id VARCHAR(255),
- user_name VARCHAR(255),
- client_id VARCHAR(255)
-);
-
-CREATE TABLE oauth_access_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication_id VARCHAR(255),
- user_name VARCHAR(255),
- client_id VARCHAR(255),
- authentication BLOB,
- refresh_token VARCHAR(255)
-);
-
-CREATE TABLE oauth_refresh_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication BLOB
-);
-
-CREATE TABLE oauth_code (
- activationCode VARCHAR(255),
- authentication BLOB
-);
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql b/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql
deleted file mode 100644
index 7c3fdf3..0000000
--- a/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql
+++ /dev/null
@@ -1,52 +0,0 @@
-CREATE TABLE oauth_client_details (
- client_id VARCHAR(256) PRIMARY KEY,
- resource_ids VARCHAR(256),
- client_secret VARCHAR(256),
- scope VARCHAR(256),
- authorized_grant_types VARCHAR(256),
- web_server_redirect_uri VARCHAR(256),
- authorities VARCHAR(256),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additional_information VARCHAR(4096),
- autoapprove VARCHAR(256)
-);
-
-CREATE TABLE oauth_client_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication_id VARCHAR(256),
- user_name VARCHAR(256),
- client_id VARCHAR(256)
-);
-
-CREATE TABLE oauth_access_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication_id VARCHAR(256),
- user_name VARCHAR(256),
- client_id VARCHAR(256),
- authentication BYTEA,
- refresh_token VARCHAR(256)
-);
-
-CREATE TABLE oauth_refresh_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication BYTEA
-);
-
-CREATE TABLE oauth_code (
- code VARCHAR(256),
- authentication BYTEA
-);
-
-CREATE TABLE oauth_approvals (
- userId VARCHAR(256),
- clientId VARCHAR(256),
- scope VARCHAR(256),
- status VARCHAR(10),
- expiresAt TIMESTAMP,
- lastModifiedAt TIMESTAMP
-);
-
diff --git a/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql b/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql
deleted file mode 100644
index 122b1fc..0000000
--- a/chapter07/01 - postgresql/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql
+++ /dev/null
@@ -1,16 +0,0 @@
-CREATE TABLE copsboot_user (
- id UUID NOT NULL,
- email VARCHAR(255),
- password VARCHAR(255),
- PRIMARY KEY (id)
-);
-
-CREATE TABLE user_roles (
- user_id UUID NOT NULL,
- roles VARCHAR(255)
-);
-
-ALTER TABLE user_roles
- ADD CONSTRAINT FK7je59ku3x462eqxu4ss3das1s
-FOREIGN KEY (user_id)
-REFERENCES copsboot_user;
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/CopsbootApplicationTests.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
index add5a9b..73e7b68 100644
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
@@ -1,19 +1,13 @@
package com.example.copsboot;
-import com.example.copsboot.infrastructure.SpringProfiles;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-@RunWith(SpringRunner.class)
@SpringBootTest
-@ActiveProfiles(SpringProfiles.TEST)
-public class CopsbootApplicationTests {
+class CopsbootApplicationTests {
- @Test
- public void contextLoads() {
- }
+ @Test
+ void contextLoads() {
+ }
}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java
deleted file mode 100644
index 71946be..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-
-import com.example.copsboot.user.UserRepository;
-import com.example.copsboot.user.Users;
-import org.junit.Test;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ApplicationUserDetailsServiceTest {
-
- @Test
- public void givenExistingUsername_whenLoadingUser_userIsReturned() {
- UserRepository repository = mock(UserRepository.class);
- ApplicationUserDetailsService service = new ApplicationUserDetailsService(repository); // <1>
- when(repository.findByEmailIgnoreCase(Users.OFFICER_EMAIL)) // <2>
- .thenReturn(Optional.of(Users.officer()));
-
- UserDetails userDetails = service.loadUserByUsername(Users.OFFICER_EMAIL); //<3>
- assertThat(userDetails).isNotNull();
- assertThat(userDetails.getUsername()).isEqualTo(Users.OFFICER_EMAIL); //<4>
- assertThat(userDetails.getAuthorities()).extracting(GrantedAuthority::getAuthority)
- .contains("ROLE_OFFICER"); //<5>
- assertThat(userDetails).isInstanceOfSatisfying(ApplicationUserDetails.class, //<6>
- applicationUserDetails -> {
- assertThat(applicationUserDetails.getUserId())
- .isEqualTo(Users.officer().getId());
- });
- }
-
- @Test//(expected = UsernameNotFoundException.class) //<7>
- public void givenNotExistingUsername_whenLoadingUser_exceptionThrown() {
- UserRepository repository = mock(UserRepository.class);
- ApplicationUserDetailsService service = new ApplicationUserDetailsService(repository);
- when(repository.findByEmailIgnoreCase(anyString())).thenReturn(Optional.empty());
-
- assertThatThrownBy(() -> service.loadUserByUsername("i@donotexist.com"))
- .isInstanceOf(UsernameNotFoundException.class);
-
- //service.loadUserByUsername("i@donotexist.com");
-
- }
-}
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java
deleted file mode 100644
index 9357ee6..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.infrastructure.SpringProfiles;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest //<1>
-@AutoConfigureMockMvc //<2>
-@ActiveProfiles(SpringProfiles.TEST)
-public class OAuth2ServerConfigurationTest {
-
- @Autowired
- private MockMvc mvc; //<3>
-
- @Autowired
- private UserService userService; //<4>
-
- @Test
- public void testGetAccessTokenAsOfficer() throws Exception {
-
- userService.createOfficer(Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD); //<5>
-
- String clientId = "test-client-id";
- String clientSecret = "test-client-secret";
-
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add("grant_type", "password");
- params.add("client_id", clientId);
- params.add("client_secret", clientSecret);
- params.add("username", Users.OFFICER_EMAIL);
- params.add("password", Users.OFFICER_PASSWORD);
-
- mvc.perform(post("/oauth/token") //<6>
- .params(params) //<7>
- .with(httpBasic(clientId, clientSecret)) //<8>
- .accept("application/json;charset=UTF-8"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json;charset=UTF-8"))
- .andDo(print()) //<9>
- .andExpect(jsonPath("access_token").isString()) //<10>
- .andExpect(jsonPath("token_type").value("bearer"))
- .andExpect(jsonPath("refresh_token").isString())
- .andExpect(jsonPath("expires_in").isNumber())
- .andExpect(jsonPath("scope").value("mobile_app"))
- ;
- }
-
-}
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java
deleted file mode 100644
index af48af9..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.boot.json.JacksonJsonParser;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.ResultActions;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-public class SecurityHelperForMockMvc {
-
- private static final String UNIT_TEST_CLIENT_ID = "test-client-id"; //<1>
- private static final String UNIT_TEST_CLIENT_SECRET = "test-client-secret"; //<2>
-
- public static final String HEADER_AUTHORIZATION = "Authorization";
-
- /**
- * Allows to get an access token for the given user in the context of a spring (unit) test
- * using MockMVC.
- *
- * @param mvc the MockMvc instance
- * @param username the username
- * @param password the password
- * @return the access_token
to be used in the Authorization
header
- * @throws Exception if no token could be obtained.
- */
- public static String obtainAccessToken(MockMvc mvc, String username, String password) throws Exception {
-
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add("grant_type", "password");
- params.add("client_id", UNIT_TEST_CLIENT_ID);
- params.add("client_secret", UNIT_TEST_CLIENT_SECRET);
- params.add("username", username);
- params.add("password", password);
-
- ResultActions result
- = mvc.perform(post("/oauth/token")
- .params(params)
- .with(httpBasic(UNIT_TEST_CLIENT_ID, UNIT_TEST_CLIENT_SECRET))
- .accept("application/json;charset=UTF-8"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json;charset=UTF-8"));
-
- String resultString = result.andReturn().getResponse().getContentAsString();
-
- JacksonJsonParser jsonParser = new JacksonJsonParser();
- return jsonParser.parseMap(resultString).get("access_token").toString();
- }
-
- public static String bearer(String accessToken) {
- return "Bearer " + accessToken;
- }
-}
\ No newline at end of file
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
deleted file mode 100644
index 5cc112c..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.Users;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-public class StubUserDetailsService implements UserDetailsService {
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- switch (username) {
- case Users.OFFICER_EMAIL:
- return new ApplicationUserDetails(Users.officer());
- case Users.CAPTAIN_EMAIL:
- return new ApplicationUserDetails(Users.captain());
- default:
- throw new UsernameNotFoundException(username);
- }
- }
-}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
new file mode 100644
index 0000000..3ddeac0
--- /dev/null
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
@@ -0,0 +1,30 @@
+package com.example.copsboot.infrastructure.test;
+
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.restdocs.RestDocumentationExtension;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+//tag::class[]
+@Retention(RetentionPolicy.RUNTIME)
+@CopsbootControllerTest
+@ExtendWith(RestDocumentationExtension.class)
+@AutoConfigureRestDocs
+@ContextConfiguration(classes = CopsbootControllerDocumentationTestConfiguration.class)
+public @interface CopsbootControllerDocumentationTest {
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
+ Class>[] value() default {};
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "controllers") //<6>
+ Class>[] controllers() default {};
+}
+//end::class[]
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
new file mode 100644
index 0000000..02e070e
--- /dev/null
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
@@ -0,0 +1,21 @@
+package com.example.copsboot.infrastructure.test;
+
+import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders;
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
+
+@TestConfiguration
+class CopsbootControllerDocumentationTestConfiguration {
+ @Bean
+ public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
+ return configurer -> configurer.operationPreprocessors()
+ .withRequestDefaults(prettyPrint())
+ .withResponseDefaults(prettyPrint(),
+ modifyHeaders().removeMatching("X.*")
+ .removeMatching("Pragma")
+ .removeMatching("Expires"));
+ }
+ }
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
index c33238a..6696635 100644
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
@@ -1,10 +1,10 @@
package com.example.copsboot.infrastructure.test;
-import com.example.copsboot.infrastructure.SpringProfiles;
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.ContextConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -12,23 +12,12 @@
/**
* Custom annotation for all {@link org.springframework.stereotype.Controller Controller} tests on the project. By using
* this single annotation, everything is configured properly to test a controller:
- *
- * - Import of {@link CopsbootControllerTestConfiguration}
- * test
profile active
- *
- *
- * Example usage:
- *
- * @RunWith(SpringRunner.class)
- * @CopsbootControllerTest(UserController.class)
- * public class UserControllerTest {
- *
*/
//tag::class[]
-@Retention(RetentionPolicy.RUNTIME) //<1>
-@WebMvcTest //<2>
-@ContextConfiguration(classes = CopsbootControllerTestConfiguration.class) //<3>
-@ActiveProfiles(SpringProfiles.TEST) //<4>
+@Retention(RetentionPolicy.RUNTIME) //<.>
+@WebMvcTest //<.>
+@AutoConfigureAddonsWebmvcResourceServerSecurity //<.>
+@Import(WebSecurityConfiguration.class) //<.>
public @interface CopsbootControllerTest {
@AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
deleted file mode 100644
index 7231430..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.example.copsboot.infrastructure.test;
-
-import com.example.copsboot.infrastructure.security.OAuth2ServerConfiguration;
-import com.example.copsboot.infrastructure.security.SecurityConfiguration;
-import com.example.copsboot.infrastructure.security.StubUserDetailsService;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
-
-@TestConfiguration
-@Import(OAuth2ServerConfiguration.class)
-public class CopsbootControllerTestConfiguration {
- @Bean
- public UserDetailsService userDetailsService() {
- return new StubUserDetailsService();
- }
-
- @Bean
- public SecurityConfiguration securityConfiguration() {
- return new SecurityConfiguration();
- }
-
-}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/UserRepositoryTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
index 3217c4a..b37e583 100644
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
@@ -1,16 +1,12 @@
package com.example.copsboot.user;
-import com.example.copsboot.infrastructure.SpringProfiles;
import com.example.orm.jpa.InMemoryUniqueIdGenerator;
import com.example.orm.jpa.UniqueIdGenerator;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashSet;
import java.util.Locale;
@@ -19,9 +15,7 @@
import static org.assertj.core.api.Assertions.assertThat;
-@RunWith(SpringRunner.class)
@DataJpaTest
-@ActiveProfiles(SpringProfiles.TEST)
public class UserRepositoryTest {
@Autowired
@@ -30,50 +24,16 @@ public class UserRepositoryTest {
//tag::testStoreUser[]
@Test
public void testStoreUser() {
- HashSet roles = new HashSet<>();
- roles.add(UserRole.OFFICER);
- User user = repository.save(new User(repository.nextId(), //<1>
- "alex.foley@beverly-hills.com",
- "my-secret-pwd",
- roles));
- assertThat(user).isNotNull(); //<6>
+ User user = repository.save(new User(repository.nextId(),
+ "alex.foley@beverly-hills.com",
+ new AuthServerId(UUID.randomUUID()),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ assertThat(user).isNotNull();
- assertThat(repository.count()).isEqualTo(1L); //<7>
+ assertThat(repository.count()).isEqualTo(1L);
}
//end::testStoreUser[]
- //tag::find-by-email-tests[]
- @Test
- public void testFindByEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail());
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
-
- @Test
- public void testFindByEmailIgnoringCase() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail()
- .toUpperCase(Locale.US));
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
-
- @Test
- public void testFindByEmail_unknownEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase("will.not@find.me");
-
- assertThat(optional).isEmpty();
- }
- //end::find-by-email-tests[]
-
//tag::testconfig[]
@TestConfiguration
static class TestConfig {
@@ -83,4 +43,4 @@ public UniqueIdGenerator generator() {
}
}
//end::testconfig[]
-}
\ No newline at end of file
+}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/Users.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/Users.java
deleted file mode 100644
index 0020a96..0000000
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/Users.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-
-import java.util.UUID;
-
-public class Users {
- private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
-
- public static final String OFFICER_EMAIL = "officer@example.com";
- public static final String OFFICER_PASSWORD = "officer";
- public static final String CAPTAIN_EMAIL = "captain@example.com";
- public static final String CAPTAIN_PASSWORD = "captain";
-
- private static User OFFICER = User.createOfficer(newRandomId(),
- OFFICER_EMAIL,
- PASSWORD_ENCODER.encode(OFFICER_PASSWORD));
-
- private static User CAPTAIN = User.createCaptain(newRandomId(),
- CAPTAIN_EMAIL,
- PASSWORD_ENCODER.encode(CAPTAIN_PASSWORD));
-
-
- public static UserId newRandomId() {
- return new UserId(UUID.randomUUID());
- }
-
- public static User newRandomOfficer() {
- return newRandomOfficer(newRandomId());
- }
-
- public static User newRandomOfficer(UserId userId) {
- String uniqueId = userId.asString().substring(0, 5);
- return User.createOfficer(userId,
- "user-" + uniqueId +
- "@example.com",
- PASSWORD_ENCODER.encode("user"));
- }
-
- public static User officer() {
- return OFFICER;
- }
-
- public static User captain() {
- return CAPTAIN;
- }
-
- private Users() {
- }
-
- public static User newOfficer(String email, String password) {
- return User.createOfficer(newRandomId(), email, PASSWORD_ENCODER.encode(password));
- }
-}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
index e0d24b0..805c501 100644
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
@@ -1,133 +1,94 @@
package com.example.copsboot.user.web;
-import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.infrastructure.test.CopsbootControllerDocumentationTest;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
-import org.springframework.restdocs.JUnitRestDocumentation;
-import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.context.WebApplicationContext;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
-import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
-import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
-import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
-@CopsbootControllerTest(UserRestController.class)
+@CopsbootControllerDocumentationTest(UserRestController.class)
public class UserRestControllerDocumentation {
-//end::class-annotations[]
- @Rule
- public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
-
- private MockMvc mvc;
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
private UserService service;
- //tag::setup-method[]
- @Autowired
- private WebApplicationContext context; //<1>
- private RestDocumentationResultHandler resultHandler; //<2>
-
- @Before
- public void setUp() {
- resultHandler = document("{method-name}", //<3>
- preprocessRequest(prettyPrint()), //<4>
- preprocessResponse(prettyPrint(), //<5>
- removeMatchingHeaders("X.*", //<6>
- "Pragma",
- "Expires")));
- mvc = MockMvcBuilders.webAppContextSetup(context) //<7>
- .apply(springSecurity()) //<8>
- .apply(documentationConfiguration(restDocumentation)) //<9>
- .alwaysDo(resultHandler) //<10>
- .build();
- }
- //end::setup-method[]
-
//tag::not-logged-in[]
@Test
public void ownUserDetailsWhenNotLoggedInExample() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized())
+ .andDo(document("own-details-unauthorized"));
}
//end::not-logged-in[]
//tag::officer-details[]
@Test
public void authenticatedOfficerDetailsExample() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andDo(resultHandler.document(
- responseFields(
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ mockMvc.perform(MockMvcRequestBuilders.get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER"))))
+ .andExpect(status().isOk())
+ .andDo(document("own-details",
+ responseFields(
+ fieldWithPath("subject").description("The subject from the JWT token"),
+ subsectionWithPath("claims").description("The claims from the JWT token")
+ )));
}
-
//end::officer-details[]
//tag::create-officer[]
@Test
public void createOfficerExample() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters(); //<1>
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password)); //<2>
-
- mvc.perform(post("/api/users") //<3>
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters))) //<4>
- .andExpect(status().isCreated()) //<5>
- .andDo(resultHandler.document(
- requestFields( //<6>
- fieldWithPath("email")
- .description("The email address of the user to be created."),
- fieldWithPath("password")
- .description("The password for the new user.")
- ),
- responseFields( //<7>
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ UserId userId = new UserId(UUID.randomUUID());
+ when(service.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andDo(document("create-user",
+ requestFields( // <.>
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ ),
+ responseFields( // <.>
+ fieldWithPath("userId")
+ .description("The unique id of the user."),
+ fieldWithPath("email")
+ .description("The email address of the user."),
+ fieldWithPath("authServerId")
+ .description("The id of the user on the authorization server."),
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ )));
}
//end::create-officer[]
}
diff --git a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
index 9014594..2acf875 100644
--- a/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
+++ b/chapter07/01 - postgresql/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
@@ -1,97 +1,84 @@
package com.example.copsboot.user.web;
import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
+// tag::class-annotations[]
@CopsbootControllerTest(UserRestController.class)
-public class UserRestControllerTest {
-//end::class-annotations[]
- @Autowired
- private MockMvc mvc;
+class UserRestControllerTest {
+ // end::class-annotations[]
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
- private UserService service;
+ private UserService userService; //<.>
@Test
- public void givenNotAuthenticated_whenAskingMyDetails_forbidden() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ void givenUnauthenticatedUser_userInfoEndpointReturnsUnauthorized() throws Exception {
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized());
}
@Test
- public void givenAuthenticatedAsOfficer_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.OFFICER_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"))
- ;
+ void givenAuthenticatedUser_userInfoEndpointReturnsOk() throws Exception {
+ String subject = UUID.randomUUID().toString(); //<.>
+ mockMvc.perform(get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(subject)))) //<.>
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("subject").value(subject)) //<.>
+ .andExpect(jsonPath("claims").isMap());
}
@Test
- public void givenAuthenticatedAsCaptain_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.CAPTAIN_EMAIL, Users.CAPTAIN_PASSWORD);
-
- when(service.getUser(Users.captain().getId())).thenReturn(Optional.of(Users.captain()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.CAPTAIN_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles").value("CAPTAIN"));
+ void givenAuthenticatedOfficer_userIsCreated() throws Exception { //<.>
+ UserId userId = new UserId(UUID.randomUUID());
+ when(userService.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("userId").value(userId.asString()))
+ .andExpect(jsonPath("email").value("wim@example.com"))
+ .andExpect(jsonPath("authServerId").value("eaa8b8a5-a264-48be-98de-d8b4ae2750ac"));
}
@Test
- public void testCreateOfficer() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters();
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password));
-
- mvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters)))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(email))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"));
-
- verify(service).createOfficer(email, password);
+ void givenAuthenticatedUserThatIsNotAnOfficer_forbiddenIsReturned() throws Exception {
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString())))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isForbidden()); // <.>
}
}
diff --git a/chapter07/01 - postgresql/src/test/resources/application-test.properties b/chapter07/01 - postgresql/src/test/resources/application-test.properties
deleted file mode 100644
index 02b4003..0000000
--- a/chapter07/01 - postgresql/src/test/resources/application-test.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-copsboot-security.mobile-app-client-id=test-client-id
-copsboot-security.mobile-app-client-secret=test-client-secret
-
-spring.flyway.locations=classpath:db/migration/h2
-spring.jpa.hibernate.ddl-auto=create-drop
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.jar b/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.properties b/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..2e76e18
--- /dev/null
+++ b/chapter07/02 - testcontainers/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/chapter07/02 - testcontainers/docker-compose.yaml b/chapter07/02 - testcontainers/docker-compose.yaml
new file mode 100644
index 0000000..92cea56
--- /dev/null
+++ b/chapter07/02 - testcontainers/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: '3'
+services:
+ db:
+ image: 'postgres:16.0'
+ ports:
+ - 5432:5432
+ environment:
+ POSTGRES_PASSWORD: my-postgres-db-pwd
+ identity:
+ image: 'quay.io/keycloak/keycloak:22.0.1'
+ entrypoint: /opt/keycloak/bin/kc.sh start-dev --import-realm
+ ports:
+ - '8180:8080'
+ environment:
+ KEYCLOAK_LOGLEVEL: 'INFO'
+ KEYCLOAK_ADMIN: 'admin'
+ KEYCLOAK_ADMIN_PASSWORD: 'admin-secret'
+ KC_HOSTNAME: 'localhost'
+ KC_HEALTH_ENABLED: 'true'
+ KC_METRICS_ENABLED: 'true'
diff --git a/chapter07/02 - testcontainers/mvnw b/chapter07/02 - testcontainers/mvnw
index 5bf251c..66df285 100755
--- a/chapter07/02 - testcontainers/mvnw
+++ b/chapter07/02 - testcontainers/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
+# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@@ -27,7 +27,6 @@
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -36,6 +35,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@@ -50,7 +53,7 @@ fi
cygwin=false;
darwin=false;
mingw=false
-case "`uname`" in
+case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
@@ -58,9 +61,9 @@ case "`uname`" in
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
- export JAVA_HOME="/Library/Java/Home"
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
@@ -68,69 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ JAVA_HOME=$(java-config --jre-home)
fi
fi
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
-
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
-
- saveddir=`pwd`
-
- M2_HOME=`dirname "$PRG"`/..
-
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
-
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
-
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
-# For Migwn, ensure paths are in UNIX format before anything is touched
+# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@@ -146,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
- JAVACMD="`which java`"
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@@ -160,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
-
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
@@ -181,45 +150,159 @@ find_maven_basedir() {
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
- echo "${basedir}"
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
fi
}
-BASE_DIR=`find_maven_basedir "$(pwd)"`
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-echo $MAVEN_PROJECTBASEDIR
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/chapter07/02 - testcontainers/mvnw.cmd b/chapter07/02 - testcontainers/mvnw.cmd
index 019bd74..95ba6f5 100644
--- a/chapter07/02 - testcontainers/mvnw.cmd
+++ b/chapter07/02 - testcontainers/mvnw.cmd
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,15 +18,14 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -35,7 +34,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -44,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
@@ -115,11 +116,72 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@@ -129,15 +191,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-exit /B %ERROR_CODE%
+cmd /C exit /B %ERROR_CODE%
diff --git a/chapter07/02 - testcontainers/pom.xml b/chapter07/02 - testcontainers/pom.xml
index 059f9dd..43db322 100644
--- a/chapter07/02 - testcontainers/pom.xml
+++ b/chapter07/02 - testcontainers/pom.xml
@@ -1,231 +1,208 @@
-
- 4.0.0
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.2
+
+
+ com.example
+ copsboot
+ 0.0.1-SNAPSHOT
+ copsboot
+ Demo project for Spring Boot
+
+
+ 17
+ 27.1-jre
+
+
- com.example.copsboot
- copsboot
- 0.0.1-SNAPSHOT
- jar
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+
+ com.c4-soft.springaddons
+ spring-addons-starter-oidc
+ 7.1.9
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
- copsboot
- Demo project for Spring Boot
+
+ com.google.guava
+ guava
+ ${guava.version}
+
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.4.RELEASE
-
-
-
-
-
- UTF-8
- UTF-8
- 1.8
-
-
- 1.5.6
-
-
- 29.0-jre
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+
+ org.flywaydb
+ flyway-core
+
+
-
- 2.0.3.RELEASE
- 1.11.2
-
-
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.c4-soft.springaddons
+ spring-addons-starter-oidc-test
+ 7.1.9
+ test
+
+
+
+ org.springframework.restdocs
+ spring-restdocs-mockmvc
+ test
+
+
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.security.oauth.boot
- spring-security-oauth2-autoconfigure
- 2.1.4.RELEASE
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
- com.google.guava
- guava
- ${guava.version}
-
-
- org.projectlombok
- lombok
-
-
- org.postgresql
- postgresql
-
-
- org.flywaydb
- flyway-core
-
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
- org.springframework.restdocs
- spring-restdocs-mockmvc
- test
-
-
- com.h2database
- h2
- runtime
-
-
- org.assertj
- assertj-core
- test
-
-
-
- org.testcontainers
- testcontainers
- ${testcontainers.version}
- test
-
-
- org.testcontainers
- postgresql
- ${testcontainers.version}
- test
-
-
-
-
-
-
-
-
- org.asciidoctor
- asciidoctor-maven-plugin
- ${asciidoctor-maven-plugin.version}
-
-
- org.asciidoctor
- asciidoctorj-pdf
- 1.5.0-alpha.16
-
-
- org.asciidoctor
- asciidoctorj
- 1.5.7
-
-
- org.springframework.restdocs
- spring-restdocs-asciidoctor
- ${spring-restdocs.version}
-
-
- org.jruby
- jruby-complete
- 9.1.17.0
-
-
-
-
- generate-docs
- prepare-package
-
- process-asciidoc
-
-
- html
-
-
-
- generate-docs-pdf
- prepare-package
-
- process-asciidoc
-
-
- pdf
-
-
-
-
- html
- book
-
- ${project.version}
-
-
-
-
-
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ true
+ false
+
+ **/*.java
+
+
+
+
+
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ 2.2.1
+
+
+ generate-docs
+ prepare-package
+
+ process-asciidoc
+
+
+ html
+
+
+
+ generate-docs-pdf
+ prepare-package
+
+ process-asciidoc
+
+
+ pdf
+
+
+
+
+
+ org.springframework.restdocs
+ spring-restdocs-asciidoctor
+ ${spring-restdocs.version}
+
+
+ org.asciidoctor
+ asciidoctorj-pdf
+ 2.3.9
+
+
+
+ book
+
+ ${project.version}
+
+
+
+
+
+
+
+
+
+
+ ci
+
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${maven-surefire-plugin.version}
-
- true
- false
-
- **/*.java
-
-
-
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+
-
-
-
-
- ci
-
-
-
- org.asciidoctor
- asciidoctor-maven-plugin
- ${asciidoctor-maven-plugin.version}
-
-
- generate-docs
- prepare-package
-
- process-asciidoc
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/chapter07/02 - testcontainers/src/main/asciidoc/Copsboot REST API Guide.adoc b/chapter07/02 - testcontainers/src/docs/asciidoc/Copsboot REST API Guide.adoc
similarity index 91%
rename from chapter07/02 - testcontainers/src/main/asciidoc/Copsboot REST API Guide.adoc
rename to chapter07/02 - testcontainers/src/docs/asciidoc/Copsboot REST API Guide.adoc
index 255bc8e..b0b91ae 100644
--- a/chapter07/02 - testcontainers/src/main/asciidoc/Copsboot REST API Guide.adoc
+++ b/chapter07/02 - testcontainers/src/docs/asciidoc/Copsboot REST API Guide.adoc
@@ -11,4 +11,4 @@ The Copsboot project uses a REST API for interfacing with the server.
This documentation covers version {project-version} of the application.
-include::_users.adoc[]
\ No newline at end of file
+include::_users.adoc[]
diff --git a/chapter07/02 - testcontainers/src/main/asciidoc/_users.adoc b/chapter07/02 - testcontainers/src/docs/asciidoc/_users.adoc
similarity index 56%
rename from chapter07/02 - testcontainers/src/main/asciidoc/_users.adoc
rename to chapter07/02 - testcontainers/src/docs/asciidoc/_users.adoc
index a033db8..2becf75 100644
--- a/chapter07/02 - testcontainers/src/main/asciidoc/_users.adoc
+++ b/chapter07/02 - testcontainers/src/docs/asciidoc/_users.adoc
@@ -7,12 +7,12 @@ The API allows to get information on the currently logged on user
via a `GET` on `/api/users/me`. If you are not a logged on user, the
following response will be returned:
-operation::own-user-details-when-not-logged-in-example[snippets='http-request,http-response']
+operation::own-details-unauthorized[snippets='http-request,http-response']
//end::initial-doc[]
If you do log on as a user, you get more information on that user:
-operation::authenticated-officer-details-example[snippets='http-request,http-response,response-fields']
+operation::own-details[snippets='http-request,http-response,response-fields']
//tag::create-user[]
@@ -20,5 +20,5 @@ operation::authenticated-officer-details-example[snippets='http-request,http-res
To create an new user, do a `POST` on `/api/users`:
-operation::create-officer-example[snippets='http-request,request-fields,http-response,response-fields']
-//end::create-user[]
\ No newline at end of file
+operation::create-user[snippets='http-request,request-fields,http-response,response-fields']
+//end::create-user[]
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplication.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplication.java
index f4e3307..7b031d7 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplication.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplication.java
@@ -1,40 +1,13 @@
package com.example.copsboot;
-import com.example.orm.jpa.InMemoryUniqueIdGenerator;
-import com.example.orm.jpa.UniqueIdGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
-import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
-
-import javax.sql.DataSource;
-import java.util.UUID;
@SpringBootApplication
public class CopsbootApplication {
- public static void main(String[] args) {
- SpringApplication.run(CopsbootApplication.class, args);
- }
-
- @Bean
- public UniqueIdGenerator uniqueIdGenerator() {
- return new InMemoryUniqueIdGenerator();
- }
-
- //tag::supporting-beans[]
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
+ public static void main(String[] args) {
+ SpringApplication.run(CopsbootApplication.class, args);
+ }
- @Bean
- public TokenStore tokenStore() {
- return new InMemoryTokenStore();
- }
- //end::supporting-beans[]
}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java
new file mode 100644
index 0000000..cb552d7
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/CopsbootApplicationConfiguration.java
@@ -0,0 +1,18 @@
+package com.example.copsboot;
+
+import com.example.orm.jpa.InMemoryUniqueIdGenerator;
+import com.example.orm.jpa.UniqueIdGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.UUID;
+
+@Configuration
+public class CopsbootApplicationConfiguration {
+
+ @Bean
+ public UniqueIdGenerator uniqueIdGenerator() {
+ return new InMemoryUniqueIdGenerator();
+ }
+
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java
deleted file mode 100644
index 74f702f..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/DevelopmentDbInitializer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.example.copsboot;
-
-import com.example.copsboot.infrastructure.SpringProfiles;
-import com.example.copsboot.user.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.context.annotation.Profile;
-import org.springframework.stereotype.Component;
-
-@Component //<1>
-@Profile(SpringProfiles.DEV) //<2>
-public class DevelopmentDbInitializer implements ApplicationRunner {
-
- private final UserService userService;
-
- @Autowired
- public DevelopmentDbInitializer(UserService userService) { //<3>
- this.userService = userService;
- }
-
- @Override
- public void run(ApplicationArguments applicationArguments) { //<4>
- createTestUsers();
- }
-
- private void createTestUsers() {
- userService.createOfficer("officer@example.com", "officer"); //<5>
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/SpringProfiles.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/SpringProfiles.java
index 344a5fe..fb1cc59 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/SpringProfiles.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/SpringProfiles.java
@@ -6,6 +6,7 @@ public final class SpringProfiles {
public static final String TEST = "test";
public static final String STAGING = "staging";
public static final String PROD = "prod";
+ public static final String REPOSITORY_TEST = "repository-test";
public static final String INTEGRATION_TEST = "integration-test";
private SpringProfiles() {
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java
deleted file mode 100644
index d541b38..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/json/EntityIdJsonSerializer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.example.copsboot.infrastructure.json;
-
-import com.example.orm.jpa.EntityId;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import org.springframework.boot.jackson.JsonComponent;
-
-import java.io.IOException;
-
-@JsonComponent //<1>
-public class EntityIdJsonSerializer extends JsonSerializer { //<2>
-
- @Override
- public void serialize(EntityId entityId, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
- jsonGenerator.writeString(entityId.asString()); //<3>
- }
-
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java
deleted file mode 100644
index 8d02905..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetails.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserId;
-import com.example.copsboot.user.UserRole;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-import java.util.Collection;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class ApplicationUserDetails extends org.springframework.security.core.userdetails.User {
-
- private static final String ROLE_PREFIX = "ROLE_";
-
- private final UserId userId;
-
- public ApplicationUserDetails(User user) {
- super(user.getEmail(), user.getPassword(), createAuthorities(user.getRoles()));
- this.userId = user.getId();
- }
-
- public UserId getUserId() {
- return userId;
- }
-
- private static Collection createAuthorities(Set roles) {
- return roles.stream()
- .map(userRole -> new SimpleGrantedAuthority(ROLE_PREFIX + userRole.name()))
- .collect(Collectors.toSet());
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java
deleted file mode 100644
index e8dc16a..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsService.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-
-import static java.lang.String.format;
-
-@Service //<1>
-public class ApplicationUserDetailsService implements UserDetailsService {
-
- private final UserRepository userRepository;
-
- @Autowired
- public ApplicationUserDetailsService(UserRepository userRepository) { // <2>
- this.userRepository = userRepository;
- }
-
- @Override
- public UserDetails loadUserByUsername(String username) {
- User user = userRepository.findByEmailIgnoreCase(username) //<3>
- .orElseThrow(() -> new UsernameNotFoundException( //<4>
- String.format("User with email %s could not be found",
- username)));
- return new ApplicationUserDetails(user); //<5>
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java
deleted file mode 100644
index e8ad97c..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfiguration.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
-import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
-import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
-import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
-import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-
-@Configuration
-public class OAuth2ServerConfiguration {
-
- private static final String RESOURCE_ID = "copsboot-service";
-
- @Configuration
- @EnableResourceServer
- @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
- protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
-
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
- resources.resourceId(RESOURCE_ID);
- }
-
- //tag::configure[]
- @Override
- public void configure(HttpSecurity http) throws Exception {
-
- http.authorizeRequests()
- .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
- .and()
- .antMatcher("/api/**")
- .authorizeRequests()
- .antMatchers(HttpMethod.POST, "/api/users").permitAll() //<1>
- .anyRequest().authenticated();
- }
- //end::configure[]
- }
-
- @Configuration
- @EnableAuthorizationServer
- protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
-
- @Autowired
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private UserDetailsService userDetailsService;
-
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @Autowired
- private TokenStore tokenStore;
-
- @Autowired
- private SecurityConfiguration securityConfiguration;
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security.passwordEncoder(passwordEncoder);
- }
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- clients.inMemory()
- .withClient(securityConfiguration.getMobileAppClientId())
- .authorizedGrantTypes("password", "refresh_token")
- .scopes("mobile_app")
- .resourceIds(RESOURCE_ID)
- .secret(passwordEncoder.encode(securityConfiguration.getMobileAppClientSecret()));
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.tokenStore(tokenStore)
- .authenticationManager(authenticationManager)
- .userDetailsService(userDetailsService);
- }
- }
-
- @Configuration
- public static class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
-
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java
deleted file mode 100644
index c246162..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/SecurityConfiguration.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-@Component //<1>
-@ConfigurationProperties(prefix = "copsboot-security") //<2>
-public class SecurityConfiguration {
- private String mobileAppClientId;
- private String mobileAppClientSecret;
-
- public String getMobileAppClientId() {
- return mobileAppClientId;
- }
-
- public void setMobileAppClientId(String mobileAppClientId) {
- this.mobileAppClientId = mobileAppClientId;
- }
-
- public String getMobileAppClientSecret() {
- return mobileAppClientSecret;
- }
-
- public void setMobileAppClientSecret(String mobileAppClientSecret) {
- this.mobileAppClientSecret = mobileAppClientSecret;
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java
new file mode 100644
index 0000000..9fca2b6
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/infrastructure/security/WebSecurityConfiguration.java
@@ -0,0 +1,19 @@
+package com.example.copsboot.infrastructure.security;
+
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.ResourceServerExpressionInterceptUrlRegistryPostProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+
+@Configuration
+@EnableMethodSecurity //<.>
+public class WebSecurityConfiguration {
+
+ @Bean
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { //<.>
+ return registry -> registry.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
+ .requestMatchers("/api/**").authenticated()
+ .anyRequest().authenticated();
+ }
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerId.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerId.java
new file mode 100644
index 0000000..1705863
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerId.java
@@ -0,0 +1,11 @@
+package com.example.copsboot.user;
+
+import org.springframework.util.Assert;
+
+import java.util.UUID;
+
+public record AuthServerId(UUID value) {
+ public AuthServerId {
+ Assert.notNull(value, "The AuthServerId value should not be null");
+ }
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java
new file mode 100644
index 0000000..f2c86b3
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/AuthServerIdAttributeConverter.java
@@ -0,0 +1,19 @@
+package com.example.copsboot.user;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+import java.util.UUID;
+
+@Converter(autoApply = true)
+public class AuthServerIdAttributeConverter implements AttributeConverter {
+ @Override
+ public UUID convertToDatabaseColumn(AuthServerId attribute) {
+ return attribute.value();
+ }
+
+ @Override
+ public AuthServerId convertToEntityAttribute(UUID dbData) {
+ return new AuthServerId(dbData);
+ }
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/CreateUserParameters.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/CreateUserParameters.java
new file mode 100644
index 0000000..2f7b0b2
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/CreateUserParameters.java
@@ -0,0 +1,4 @@
+package com.example.copsboot.user;
+
+public record CreateUserParameters(AuthServerId authServerId, String email, String mobileToken) {
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/User.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/User.java
index 236cd6d..32d02a4 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/User.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/User.java
@@ -1,53 +1,37 @@
package com.example.copsboot.user;
import com.example.orm.jpa.AbstractEntity;
-import com.google.common.collect.Sets;
-
-import javax.persistence.*;
-import javax.validation.constraints.NotNull;
-import java.util.Set;
-
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
@Entity
@Table(name = "copsboot_user")
public class User extends AbstractEntity {
private String email;
- private String password;
-
- @ElementCollection(fetch = FetchType.EAGER)
- @Enumerated(EnumType.STRING)
- @NotNull
- private Set roles;
+ private AuthServerId authServerId; //<.>
+ private String mobileToken; //<.>
protected User() {
}
- public User(UserId id, String email, String password, Set roles) {
+ public User(UserId id, String email, AuthServerId authServerId, String mobileToken) { //<.>
super(id);
this.email = email;
- this.password = password;
- this.roles = roles;
- }
-
- public static User createOfficer(UserId userId, String email, String encodedPassword) {
- return new User(userId, email, encodedPassword, Sets.newHashSet(UserRole.OFFICER));
- }
-
- public static User createCaptain(UserId userId, String email, String encodedPassword) {
- return new User(userId, email, encodedPassword, Sets.newHashSet(UserRole.CAPTAIN));
+ this.authServerId = authServerId;
+ this.mobileToken = mobileToken;
}
public String getEmail() {
return email;
}
- public String getPassword() {
- return password;
+ public AuthServerId getAuthServerId() { //<.>
+ return authServerId;
}
- public Set getRoles() {
- return roles;
+ public String getMobileToken() {
+ return mobileToken;
}
}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserNotFoundException.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserNotFoundException.java
deleted file mode 100644
index 1f65f04..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserNotFoundException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(HttpStatus.NOT_FOUND) //<1>
-public class UserNotFoundException extends RuntimeException {
- public UserNotFoundException(UserId userId) {
- super(String.format("Could not find user with id %s", userId.asString()));
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserRepository.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserRepository.java
index 2359735..43f7e98 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserRepository.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserRepository.java
@@ -3,9 +3,9 @@
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
-import java.util.UUID;
+
//tag::class[]
public interface UserRepository extends CrudRepository, UserRepositoryCustom {
- Optional findByEmailIgnoreCase(String email);
+ Optional findByAuthServerId(AuthServerId authServerId);
}
//end::class[]
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserService.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserService.java
index 9e155a3..61846a5 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserService.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserService.java
@@ -1,9 +1,28 @@
package com.example.copsboot.user;
+import org.springframework.stereotype.Service;
+
import java.util.Optional;
-public interface UserService {
- User createOfficer(String email, String password);
+@Service
+public class UserService {
+ private final UserRepository repository; //<.>
+
+ public UserService(UserRepository repository) {
+ this.repository = repository;
+ }
+
+ public Optional findUserByAuthServerId(AuthServerId authServerId) { //<.>
+ return repository.findByAuthServerId(authServerId);
+ }
- Optional getUser(UserId userId);
+ // tag::createUser[]
+ public User createUser(CreateUserParameters createUserParameters) {
+ UserId userId = repository.nextId();
+ User user = new User(userId, createUserParameters.email(),
+ createUserParameters.authServerId(),
+ createUserParameters.mobileToken());
+ return repository.save(user);
+ }
+ // end::createUser[]
}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserServiceImpl.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserServiceImpl.java
deleted file mode 100644
index 9856e84..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/UserServiceImpl.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Service;
-
-import java.util.Optional;
-
-@Service
-public class UserServiceImpl implements UserService {
- private final UserRepository repository;
- private final PasswordEncoder passwordEncoder;
-
- @Autowired
- public UserServiceImpl(UserRepository repository, PasswordEncoder passwordEncoder) {
- this.repository = repository;
- this.passwordEncoder = passwordEncoder;
- }
-
- @Override
- public User createOfficer(String email, String password) {
- User user = User.createOfficer(repository.nextId(), email, passwordEncoder.encode(password));
- return repository.save(user);
- }
-
- @Override
- public Optional getUser(UserId userId) {
- return repository.findById(userId);
- }
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java
deleted file mode 100644
index 7ab85e9..0000000
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateOfficerParameters.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.example.copsboot.user.web;
-
-import lombok.Data;
-import org.hibernate.validator.constraints.Email;
-
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-
-@Data
-public class CreateOfficerParameters {
- @NotNull
- @Email
- private String email;
-
- @NotNull
- @Size(min = 6, max = 1000)
- private String password;
-}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java
new file mode 100644
index 0000000..0d8f0ab
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/CreateUserRequest.java
@@ -0,0 +1,16 @@
+package com.example.copsboot.user.web;
+
+import com.example.copsboot.user.AuthServerId;
+import com.example.copsboot.user.CreateUserParameters;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.UUID;
+
+public record CreateUserRequest(String mobileToken) { //<.>
+
+ public CreateUserParameters toParameters(Jwt jwt) {
+ AuthServerId authServerId = new AuthServerId(UUID.fromString(jwt.getSubject())); //<.>
+ String email = jwt.getClaimAsString("email"); //<.>
+ return new CreateUserParameters(authServerId, email, mobileToken);
+ }
+}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserDto.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserDto.java
index 3769d1a..2fac96c 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserDto.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserDto.java
@@ -1,21 +1,14 @@
package com.example.copsboot.user.web;
import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserId;
-import com.example.copsboot.user.UserRole;
-import lombok.Value;
-import java.util.Set;
-
-@Value
-public class UserDto {
- private final UserId id;
- private final String email;
- private final Set roles;
+import java.util.UUID;
+public record UserDto(UUID userId, String email, UUID authServerId, String mobileToken) {
public static UserDto fromUser(User user) {
- return new UserDto(user.getId(),
- user.getEmail(),
- user.getRoles());
+ return new UserDto(user.getId().getId(),
+ user.getEmail(),
+ user.getAuthServerId().value(),
+ user.getMobileToken());
}
}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserRestController.java b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserRestController.java
index c74ccd8..796adc1 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserRestController.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/copsboot/user/web/UserRestController.java
@@ -1,41 +1,52 @@
package com.example.copsboot.user.web;
-import com.example.copsboot.infrastructure.security.ApplicationUserDetails;
+import com.example.copsboot.user.AuthServerId;
+import com.example.copsboot.user.CreateUserParameters;
import com.example.copsboot.user.User;
-import com.example.copsboot.user.UserNotFoundException;
import com.example.copsboot.user.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
-import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
@RestController
@RequestMapping("/api/users")
public class UserRestController {
+ private final UserService userService;
- private final UserService service;
-
- @Autowired
- public UserRestController(UserService service) {
- this.service = service;
+ public UserRestController(UserService userService) {
+ this.userService = userService;
}
- @GetMapping("/me")
- public UserDto currentUser(@AuthenticationPrincipal ApplicationUserDetails userDetails) {
- User user = service.getUser(userDetails.getUserId())
- .orElseThrow(() -> new UserNotFoundException(userDetails.getUserId()));
- return UserDto.fromUser(user);
+ // tag::myself[]
+ @GetMapping("/me") //<.>
+ public Map myself(@AuthenticationPrincipal Jwt jwt) { //<.>
+ Optional userByAuthServerId = userService.findUserByAuthServerId(new AuthServerId(UUID.fromString(jwt.getSubject())));
+
+ Map result = new HashMap<>();
+ userByAuthServerId.ifPresent(user -> result.put("userId", user.getId().asString()));
+ result.put("subject", jwt.getSubject());
+ result.put("claims", jwt.getClaims());
+
+ return result;
}
+ // end::myself[]
- //tag::post[]
- @PostMapping //<1>
- @ResponseStatus(HttpStatus.CREATED) //<2>
- public UserDto createOfficer(@Valid @RequestBody CreateOfficerParameters parameters) { //<3>
- User officer = service.createOfficer(parameters.getEmail(), //<4>
- parameters.getPassword());
- return UserDto.fromUser(officer); //<5>
+ // tag::createUser[]
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ @PreAuthorize("hasRole('OFFICER')")
+ public UserDto createUser(@AuthenticationPrincipal Jwt jwt,
+ @RequestBody CreateUserRequest request) {
+ CreateUserParameters parameters = request.toParameters(jwt);
+ User user = userService.createUser(parameters);
+ return UserDto.fromUser(user);
}
- //end::post[]
+ // end::createUser[]
}
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntity.java b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntity.java
index dfa9f1e..275804e 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntity.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntity.java
@@ -2,8 +2,8 @@
import com.example.util.ArtifactForFramework;
-import javax.persistence.EmbeddedId;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.MappedSuperclass;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntityId.java b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntityId.java
index b9ddc5b..f50c4e4 100755
--- a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntityId.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/AbstractEntityId.java
@@ -2,7 +2,7 @@
import com.example.util.ArtifactForFramework;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Objects;
diff --git a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/Entity.java b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/Entity.java
index a573e0e..3a45231 100644
--- a/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/Entity.java
+++ b/chapter07/02 - testcontainers/src/main/java/com/example/orm/jpa/Entity.java
@@ -1,6 +1,5 @@
package com.example.orm.jpa;
-import java.io.Serializable;
/**
* Interface for entity objects.
diff --git a/chapter07/02 - testcontainers/src/main/resources/application-dev.properties b/chapter07/02 - testcontainers/src/main/resources/application-dev.properties
deleted file mode 100644
index f72b4c7..0000000
--- a/chapter07/02 - testcontainers/src/main/resources/application-dev.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-copsboot-security.mobile-app-client-id=copsboot-mobile-client
-copsboot-security.mobile-app-client-secret=ccUyb6vS4S8nxfbKPCrN
-
-spring.flyway.locations=classpath:db/migration/h2
-spring.jpa.hibernate.ddl-auto=create-drop
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/main/resources/application-local.properties b/chapter07/02 - testcontainers/src/main/resources/application-local.properties
index 8f13f3f..7e354d5 100644
--- a/chapter07/02 - testcontainers/src/main/resources/application-local.properties
+++ b/chapter07/02 - testcontainers/src/main/resources/application-local.properties
@@ -3,13 +3,9 @@ spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=my-postgres-db-pwd
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
-spring.jpa.hibernate.ddl-auto=none
+spring.jpa.hibernate.ddl-auto=validate
-copsboot-security.mobile-app-client-id=copsboot-mobile-client
-copsboot-security.mobile-app-client-secret=ccUyb6vS4S8nxfbKPCrN
-
-spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
-spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
-spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.sql
-
-spring.flyway.locations=classpath:db/migration/postgresql
\ No newline at end of file
+#spring.jpa.properties.jakarta.persistence.schema-generation.create-source=metadata
+#spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=create
+#spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-target=create.sql
+#spring.jpa.properties.hibernate.hbm2ddl.delimiter=;
diff --git a/chapter07/02 - testcontainers/src/main/resources/application.properties b/chapter07/02 - testcontainers/src/main/resources/application.properties
index e69de29..22c3363 100644
--- a/chapter07/02 - testcontainers/src/main/resources/application.properties
+++ b/chapter07/02 - testcontainers/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+com.c4-soft.springaddons.oidc.ops[0].iss=http://localhost:8180/realms/copsboot
+com.c4-soft.springaddons.oidc.ops[0].authorities[0].path=$.realm_access.roles
+com.c4-soft.springaddons.oidc.ops[0].authorities[0].prefix=ROLE_
diff --git a/chapter07/02 - testcontainers/src/main/resources/db/migration/V1.0.0.1__users.sql b/chapter07/02 - testcontainers/src/main/resources/db/migration/V1.0.0.1__users.sql
new file mode 100644
index 0000000..d1939fa
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/main/resources/db/migration/V1.0.0.1__users.sql
@@ -0,0 +1,7 @@
+CREATE TABLE copsboot_user
+(
+ id uuid NOT NULL PRIMARY KEY,
+ auth_server_id uuid,
+ email VARCHAR(255),
+ mobile_token VARCHAR(255)
+);
diff --git a/chapter07/02 - testcontainers/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql b/chapter07/02 - testcontainers/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql
deleted file mode 100644
index 485336f..0000000
--- a/chapter07/02 - testcontainers/src/main/resources/db/migration/h2/V1.0.0.1__authentication.sql
+++ /dev/null
@@ -1,42 +0,0 @@
-CREATE TABLE oauth_client_details (
- client_id VARCHAR(255) PRIMARY KEY,
- resource_ids VARCHAR(255),
- client_secret VARCHAR(255),
- scope VARCHAR(255),
- authorized_grant_types VARCHAR(255),
- web_server_redirect_uri VARCHAR(255),
- authorities VARCHAR(255),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additional_information VARCHAR(4096),
- autoapprove VARCHAR(255)
-);
-
-CREATE TABLE oauth_client_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication_id VARCHAR(255),
- user_name VARCHAR(255),
- client_id VARCHAR(255)
-);
-
-CREATE TABLE oauth_access_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication_id VARCHAR(255),
- user_name VARCHAR(255),
- client_id VARCHAR(255),
- authentication BLOB,
- refresh_token VARCHAR(255)
-);
-
-CREATE TABLE oauth_refresh_token (
- token_id VARCHAR(255),
- token BLOB,
- authentication BLOB
-);
-
-CREATE TABLE oauth_code (
- activationCode VARCHAR(255),
- authentication BLOB
-);
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql b/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql
deleted file mode 100644
index 7c3fdf3..0000000
--- a/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.1__authentication.sql
+++ /dev/null
@@ -1,52 +0,0 @@
-CREATE TABLE oauth_client_details (
- client_id VARCHAR(256) PRIMARY KEY,
- resource_ids VARCHAR(256),
- client_secret VARCHAR(256),
- scope VARCHAR(256),
- authorized_grant_types VARCHAR(256),
- web_server_redirect_uri VARCHAR(256),
- authorities VARCHAR(256),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additional_information VARCHAR(4096),
- autoapprove VARCHAR(256)
-);
-
-CREATE TABLE oauth_client_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication_id VARCHAR(256),
- user_name VARCHAR(256),
- client_id VARCHAR(256)
-);
-
-CREATE TABLE oauth_access_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication_id VARCHAR(256),
- user_name VARCHAR(256),
- client_id VARCHAR(256),
- authentication BYTEA,
- refresh_token VARCHAR(256)
-);
-
-CREATE TABLE oauth_refresh_token (
- token_id VARCHAR(256),
- token BYTEA,
- authentication BYTEA
-);
-
-CREATE TABLE oauth_code (
- code VARCHAR(256),
- authentication BYTEA
-);
-
-CREATE TABLE oauth_approvals (
- userId VARCHAR(256),
- clientId VARCHAR(256),
- scope VARCHAR(256),
- status VARCHAR(10),
- expiresAt TIMESTAMP,
- lastModifiedAt TIMESTAMP
-);
-
diff --git a/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql b/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql
deleted file mode 100644
index 122b1fc..0000000
--- a/chapter07/02 - testcontainers/src/main/resources/db/migration/postgresql/V1.0.0.2__users.sql
+++ /dev/null
@@ -1,16 +0,0 @@
-CREATE TABLE copsboot_user (
- id UUID NOT NULL,
- email VARCHAR(255),
- password VARCHAR(255),
- PRIMARY KEY (id)
-);
-
-CREATE TABLE user_roles (
- user_id UUID NOT NULL,
- roles VARCHAR(255)
-);
-
-ALTER TABLE user_roles
- ADD CONSTRAINT FK7je59ku3x462eqxu4ss3das1s
-FOREIGN KEY (user_id)
-REFERENCES copsboot_user;
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/CopsbootApplicationTests.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
index add5a9b..5feb390 100644
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/CopsbootApplicationTests.java
@@ -1,19 +1,16 @@
package com.example.copsboot;
import com.example.copsboot.infrastructure.SpringProfiles;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-@RunWith(SpringRunner.class)
@SpringBootTest
-@ActiveProfiles(SpringProfiles.TEST)
-public class CopsbootApplicationTests {
+@ActiveProfiles(SpringProfiles.INTEGRATION_TEST)
+class CopsbootApplicationTests {
- @Test
- public void contextLoads() {
- }
+ @Test
+ void contextLoads() {
+ }
}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java
deleted file mode 100644
index 71946be..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/ApplicationUserDetailsServiceTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-
-import com.example.copsboot.user.UserRepository;
-import com.example.copsboot.user.Users;
-import org.junit.Test;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ApplicationUserDetailsServiceTest {
-
- @Test
- public void givenExistingUsername_whenLoadingUser_userIsReturned() {
- UserRepository repository = mock(UserRepository.class);
- ApplicationUserDetailsService service = new ApplicationUserDetailsService(repository); // <1>
- when(repository.findByEmailIgnoreCase(Users.OFFICER_EMAIL)) // <2>
- .thenReturn(Optional.of(Users.officer()));
-
- UserDetails userDetails = service.loadUserByUsername(Users.OFFICER_EMAIL); //<3>
- assertThat(userDetails).isNotNull();
- assertThat(userDetails.getUsername()).isEqualTo(Users.OFFICER_EMAIL); //<4>
- assertThat(userDetails.getAuthorities()).extracting(GrantedAuthority::getAuthority)
- .contains("ROLE_OFFICER"); //<5>
- assertThat(userDetails).isInstanceOfSatisfying(ApplicationUserDetails.class, //<6>
- applicationUserDetails -> {
- assertThat(applicationUserDetails.getUserId())
- .isEqualTo(Users.officer().getId());
- });
- }
-
- @Test//(expected = UsernameNotFoundException.class) //<7>
- public void givenNotExistingUsername_whenLoadingUser_exceptionThrown() {
- UserRepository repository = mock(UserRepository.class);
- ApplicationUserDetailsService service = new ApplicationUserDetailsService(repository);
- when(repository.findByEmailIgnoreCase(anyString())).thenReturn(Optional.empty());
-
- assertThatThrownBy(() -> service.loadUserByUsername("i@donotexist.com"))
- .isInstanceOf(UsernameNotFoundException.class);
-
- //service.loadUserByUsername("i@donotexist.com");
-
- }
-}
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java
deleted file mode 100644
index 9357ee6..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/OAuth2ServerConfigurationTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.infrastructure.SpringProfiles;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest //<1>
-@AutoConfigureMockMvc //<2>
-@ActiveProfiles(SpringProfiles.TEST)
-public class OAuth2ServerConfigurationTest {
-
- @Autowired
- private MockMvc mvc; //<3>
-
- @Autowired
- private UserService userService; //<4>
-
- @Test
- public void testGetAccessTokenAsOfficer() throws Exception {
-
- userService.createOfficer(Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD); //<5>
-
- String clientId = "test-client-id";
- String clientSecret = "test-client-secret";
-
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add("grant_type", "password");
- params.add("client_id", clientId);
- params.add("client_secret", clientSecret);
- params.add("username", Users.OFFICER_EMAIL);
- params.add("password", Users.OFFICER_PASSWORD);
-
- mvc.perform(post("/oauth/token") //<6>
- .params(params) //<7>
- .with(httpBasic(clientId, clientSecret)) //<8>
- .accept("application/json;charset=UTF-8"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json;charset=UTF-8"))
- .andDo(print()) //<9>
- .andExpect(jsonPath("access_token").isString()) //<10>
- .andExpect(jsonPath("token_type").value("bearer"))
- .andExpect(jsonPath("refresh_token").isString())
- .andExpect(jsonPath("expires_in").isNumber())
- .andExpect(jsonPath("scope").value("mobile_app"))
- ;
- }
-
-}
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java
deleted file mode 100644
index af48af9..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/SecurityHelperForMockMvc.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import org.springframework.boot.json.JacksonJsonParser;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.ResultActions;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-public class SecurityHelperForMockMvc {
-
- private static final String UNIT_TEST_CLIENT_ID = "test-client-id"; //<1>
- private static final String UNIT_TEST_CLIENT_SECRET = "test-client-secret"; //<2>
-
- public static final String HEADER_AUTHORIZATION = "Authorization";
-
- /**
- * Allows to get an access token for the given user in the context of a spring (unit) test
- * using MockMVC.
- *
- * @param mvc the MockMvc instance
- * @param username the username
- * @param password the password
- * @return the access_token
to be used in the Authorization
header
- * @throws Exception if no token could be obtained.
- */
- public static String obtainAccessToken(MockMvc mvc, String username, String password) throws Exception {
-
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add("grant_type", "password");
- params.add("client_id", UNIT_TEST_CLIENT_ID);
- params.add("client_secret", UNIT_TEST_CLIENT_SECRET);
- params.add("username", username);
- params.add("password", password);
-
- ResultActions result
- = mvc.perform(post("/oauth/token")
- .params(params)
- .with(httpBasic(UNIT_TEST_CLIENT_ID, UNIT_TEST_CLIENT_SECRET))
- .accept("application/json;charset=UTF-8"))
- .andExpect(status().isOk())
- .andExpect(content().contentType("application/json;charset=UTF-8"));
-
- String resultString = result.andReturn().getResponse().getContentAsString();
-
- JacksonJsonParser jsonParser = new JacksonJsonParser();
- return jsonParser.parseMap(resultString).get("access_token").toString();
- }
-
- public static String bearer(String accessToken) {
- return "Bearer " + accessToken;
- }
-}
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
deleted file mode 100644
index 5cc112c..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/security/StubUserDetailsService.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.example.copsboot.infrastructure.security;
-
-import com.example.copsboot.user.Users;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-public class StubUserDetailsService implements UserDetailsService {
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- switch (username) {
- case Users.OFFICER_EMAIL:
- return new ApplicationUserDetails(Users.officer());
- case Users.CAPTAIN_EMAIL:
- return new ApplicationUserDetails(Users.captain());
- default:
- throw new UsernameNotFoundException(username);
- }
- }
-}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
new file mode 100644
index 0000000..3ddeac0
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTest.java
@@ -0,0 +1,30 @@
+package com.example.copsboot.infrastructure.test;
+
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.restdocs.RestDocumentationExtension;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+//tag::class[]
+@Retention(RetentionPolicy.RUNTIME)
+@CopsbootControllerTest
+@ExtendWith(RestDocumentationExtension.class)
+@AutoConfigureRestDocs
+@ContextConfiguration(classes = CopsbootControllerDocumentationTestConfiguration.class)
+public @interface CopsbootControllerDocumentationTest {
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
+ Class>[] value() default {};
+
+ @AliasFor(annotation = WebMvcTest.class, attribute = "controllers") //<6>
+ Class>[] controllers() default {};
+}
+//end::class[]
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
new file mode 100644
index 0000000..02e070e
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerDocumentationTestConfiguration.java
@@ -0,0 +1,21 @@
+package com.example.copsboot.infrastructure.test;
+
+import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders;
+import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
+
+@TestConfiguration
+class CopsbootControllerDocumentationTestConfiguration {
+ @Bean
+ public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
+ return configurer -> configurer.operationPreprocessors()
+ .withRequestDefaults(prettyPrint())
+ .withResponseDefaults(prettyPrint(),
+ modifyHeaders().removeMatching("X.*")
+ .removeMatching("Pragma")
+ .removeMatching("Expires"));
+ }
+ }
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
index c33238a..6696635 100644
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTest.java
@@ -1,10 +1,10 @@
package com.example.copsboot.infrastructure.test;
-import com.example.copsboot.infrastructure.SpringProfiles;
+import com.c4_soft.springaddons.security.oauth2.test.webmvc.AutoConfigureAddonsWebmvcResourceServerSecurity;
+import com.example.copsboot.infrastructure.security.WebSecurityConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.ContextConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -12,23 +12,12 @@
/**
* Custom annotation for all {@link org.springframework.stereotype.Controller Controller} tests on the project. By using
* this single annotation, everything is configured properly to test a controller:
- *
- * - Import of {@link CopsbootControllerTestConfiguration}
- * test
profile active
- *
- *
- * Example usage:
- *
- * @RunWith(SpringRunner.class)
- * @CopsbootControllerTest(UserController.class)
- * public class UserControllerTest {
- *
*/
//tag::class[]
-@Retention(RetentionPolicy.RUNTIME) //<1>
-@WebMvcTest //<2>
-@ContextConfiguration(classes = CopsbootControllerTestConfiguration.class) //<3>
-@ActiveProfiles(SpringProfiles.TEST) //<4>
+@Retention(RetentionPolicy.RUNTIME) //<.>
+@WebMvcTest //<.>
+@AutoConfigureAddonsWebmvcResourceServerSecurity //<.>
+@Import(WebSecurityConfiguration.class) //<.>
public @interface CopsbootControllerTest {
@AliasFor(annotation = WebMvcTest.class, attribute = "value") //<5>
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
deleted file mode 100644
index 7231430..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/infrastructure/test/CopsbootControllerTestConfiguration.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.example.copsboot.infrastructure.test;
-
-import com.example.copsboot.infrastructure.security.OAuth2ServerConfiguration;
-import com.example.copsboot.infrastructure.security.SecurityConfiguration;
-import com.example.copsboot.infrastructure.security.StubUserDetailsService;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.oauth2.provider.token.TokenStore;
-import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
-
-@TestConfiguration
-@Import(OAuth2ServerConfiguration.class)
-public class CopsbootControllerTestConfiguration {
- @Bean
- public UserDetailsService userDetailsService() {
- return new StubUserDetailsService();
- }
-
- @Bean
- public SecurityConfiguration securityConfiguration() {
- return new SecurityConfiguration();
- }
-
-}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryIntegrationTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryIntegrationTest.java
deleted file mode 100644
index 720f959..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryIntegrationTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.example.copsboot.user;
-
-import com.example.copsboot.infrastructure.SpringProfiles;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-@RunWith(SpringRunner.class)
-@DataJpaTest
-@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) //<1>
-@ActiveProfiles(SpringProfiles.INTEGRATION_TEST) //<2>
-public class UserRepositoryIntegrationTest {
- @Autowired
- private UserRepository repository;
- @PersistenceContext
- private EntityManager entityManager;
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Test
- public void testSaveUser() {
- Set roles = new HashSet<>();
- roles.add(UserRole.OFFICER);
- User user = repository.save(new User(repository.nextId(),
- "alex.foley@beverly-hills.com",
- "my-secret-pwd",
- roles));
- assertThat(user).isNotNull();
-
- assertThat(repository.count()).isEqualTo(1L);
-
- entityManager.flush(); //<3>
- assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM copsboot_user", Long.class)).isEqualTo(1L); //<4>
- assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM user_roles", Long.class)).isEqualTo(1L);
- assertThat(jdbcTemplate.queryForObject("SELECT roles FROM user_roles", String.class)).isEqualTo("OFFICER");
- }
-}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
index 3217c4a..19c23fe 100644
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/UserRepositoryTest.java
@@ -3,14 +3,16 @@
import com.example.copsboot.infrastructure.SpringProfiles;
import com.example.orm.jpa.InMemoryUniqueIdGenerator;
import com.example.orm.jpa.UniqueIdGenerator;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
+import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashSet;
import java.util.Locale;
@@ -19,62 +21,34 @@
import static org.assertj.core.api.Assertions.assertThat;
-@RunWith(SpringRunner.class)
@DataJpaTest
-@ActiveProfiles(SpringProfiles.TEST)
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) //<1>
+@ActiveProfiles(SpringProfiles.REPOSITORY_TEST) //<2>
public class UserRepositoryTest {
@Autowired
private UserRepository repository;
+ @PersistenceContext
+ private EntityManager entityManager;
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
- //tag::testStoreUser[]
@Test
public void testStoreUser() {
- HashSet roles = new HashSet<>();
- roles.add(UserRole.OFFICER);
- User user = repository.save(new User(repository.nextId(), //<1>
- "alex.foley@beverly-hills.com",
- "my-secret-pwd",
- roles));
- assertThat(user).isNotNull(); //<6>
-
- assertThat(repository.count()).isEqualTo(1L); //<7>
- }
- //end::testStoreUser[]
+ User user = repository.save(new User(repository.nextId(),
+ "alex.foley@beverly-hills.com",
+ new AuthServerId(UUID.randomUUID()),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ assertThat(user).isNotNull();
- //tag::find-by-email-tests[]
- @Test
- public void testFindByEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail());
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
+ assertThat(repository.count()).isEqualTo(1L);
- @Test
- public void testFindByEmailIgnoringCase() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase(user.getEmail()
- .toUpperCase(Locale.US));
-
- assertThat(optional).isNotEmpty()
- .contains(user);
- }
-
- @Test
- public void testFindByEmail_unknownEmail() {
- User user = Users.newRandomOfficer();
- repository.save(user);
- Optional optional = repository.findByEmailIgnoreCase("will.not@find.me");
+ entityManager.flush(); //<3>
- assertThat(optional).isEmpty();
+ assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM copsboot_user", Long.class)).isEqualTo(1L); //<4>
+ assertThat(jdbcTemplate.queryForObject("SELECT email FROM copsboot_user", String.class)).isEqualTo("alex.foley@beverly-hills.com");
}
- //end::find-by-email-tests[]
- //tag::testconfig[]
@TestConfiguration
static class TestConfig {
@Bean
@@ -82,5 +56,4 @@ public UniqueIdGenerator generator() {
return new InMemoryUniqueIdGenerator();
}
}
- //end::testconfig[]
-}
\ No newline at end of file
+}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/Users.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/Users.java
deleted file mode 100644
index 0020a96..0000000
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/Users.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.example.copsboot.user;
-
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-
-import java.util.UUID;
-
-public class Users {
- private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
-
- public static final String OFFICER_EMAIL = "officer@example.com";
- public static final String OFFICER_PASSWORD = "officer";
- public static final String CAPTAIN_EMAIL = "captain@example.com";
- public static final String CAPTAIN_PASSWORD = "captain";
-
- private static User OFFICER = User.createOfficer(newRandomId(),
- OFFICER_EMAIL,
- PASSWORD_ENCODER.encode(OFFICER_PASSWORD));
-
- private static User CAPTAIN = User.createCaptain(newRandomId(),
- CAPTAIN_EMAIL,
- PASSWORD_ENCODER.encode(CAPTAIN_PASSWORD));
-
-
- public static UserId newRandomId() {
- return new UserId(UUID.randomUUID());
- }
-
- public static User newRandomOfficer() {
- return newRandomOfficer(newRandomId());
- }
-
- public static User newRandomOfficer(UserId userId) {
- String uniqueId = userId.asString().substring(0, 5);
- return User.createOfficer(userId,
- "user-" + uniqueId +
- "@example.com",
- PASSWORD_ENCODER.encode("user"));
- }
-
- public static User officer() {
- return OFFICER;
- }
-
- public static User captain() {
- return CAPTAIN;
- }
-
- private Users() {
- }
-
- public static User newOfficer(String email, String password) {
- return User.createOfficer(newRandomId(), email, PASSWORD_ENCODER.encode(password));
- }
-}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
index e0d24b0..805c501 100644
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerDocumentation.java
@@ -1,133 +1,94 @@
package com.example.copsboot.user.web;
-import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.infrastructure.test.CopsbootControllerDocumentationTest;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
-import org.springframework.restdocs.JUnitRestDocumentation;
-import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.context.WebApplicationContext;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
-import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
-import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
-import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
-@CopsbootControllerTest(UserRestController.class)
+@CopsbootControllerDocumentationTest(UserRestController.class)
public class UserRestControllerDocumentation {
-//end::class-annotations[]
- @Rule
- public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
-
- private MockMvc mvc;
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
private UserService service;
- //tag::setup-method[]
- @Autowired
- private WebApplicationContext context; //<1>
- private RestDocumentationResultHandler resultHandler; //<2>
-
- @Before
- public void setUp() {
- resultHandler = document("{method-name}", //<3>
- preprocessRequest(prettyPrint()), //<4>
- preprocessResponse(prettyPrint(), //<5>
- removeMatchingHeaders("X.*", //<6>
- "Pragma",
- "Expires")));
- mvc = MockMvcBuilders.webAppContextSetup(context) //<7>
- .apply(springSecurity()) //<8>
- .apply(documentationConfiguration(restDocumentation)) //<9>
- .alwaysDo(resultHandler) //<10>
- .build();
- }
- //end::setup-method[]
-
//tag::not-logged-in[]
@Test
public void ownUserDetailsWhenNotLoggedInExample() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized())
+ .andDo(document("own-details-unauthorized"));
}
//end::not-logged-in[]
//tag::officer-details[]
@Test
public void authenticatedOfficerDetailsExample() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andDo(resultHandler.document(
- responseFields(
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ mockMvc.perform(MockMvcRequestBuilders.get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER"))))
+ .andExpect(status().isOk())
+ .andDo(document("own-details",
+ responseFields(
+ fieldWithPath("subject").description("The subject from the JWT token"),
+ subsectionWithPath("claims").description("The claims from the JWT token")
+ )));
}
-
//end::officer-details[]
//tag::create-officer[]
@Test
public void createOfficerExample() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters(); //<1>
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password)); //<2>
-
- mvc.perform(post("/api/users") //<3>
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters))) //<4>
- .andExpect(status().isCreated()) //<5>
- .andDo(resultHandler.document(
- requestFields( //<6>
- fieldWithPath("email")
- .description("The email address of the user to be created."),
- fieldWithPath("password")
- .description("The password for the new user.")
- ),
- responseFields( //<7>
- fieldWithPath("id")
- .description("The unique id of the user."),
- fieldWithPath("email")
- .description("The email address of the user."),
- fieldWithPath("roles")
- .description("The security roles of the user."))));
+ UserId userId = new UserId(UUID.randomUUID());
+ when(service.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andDo(document("create-user",
+ requestFields( // <.>
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ ),
+ responseFields( // <.>
+ fieldWithPath("userId")
+ .description("The unique id of the user."),
+ fieldWithPath("email")
+ .description("The email address of the user."),
+ fieldWithPath("authServerId")
+ .description("The id of the user on the authorization server."),
+ fieldWithPath("mobileToken")
+ .description("The unique mobile token of the device (for push notifications).")
+ )));
}
//end::create-officer[]
}
diff --git a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
index 9014594..2acf875 100644
--- a/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
+++ b/chapter07/02 - testcontainers/src/test/java/com/example/copsboot/user/web/UserRestControllerTest.java
@@ -1,97 +1,84 @@
package com.example.copsboot.user.web;
import com.example.copsboot.infrastructure.test.CopsbootControllerTest;
-import com.example.copsboot.user.UserService;
-import com.example.copsboot.user.Users;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import com.example.copsboot.user.*;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
-import java.util.Optional;
+import java.util.UUID;
-import static com.example.copsboot.infrastructure.security.SecurityHelperForMockMvc.*;
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//tag::class-annotations[]
-@RunWith(SpringRunner.class)
+// tag::class-annotations[]
@CopsbootControllerTest(UserRestController.class)
-public class UserRestControllerTest {
-//end::class-annotations[]
- @Autowired
- private MockMvc mvc;
+class UserRestControllerTest {
+ // end::class-annotations[]
@Autowired
- private ObjectMapper objectMapper;
+ private MockMvc mockMvc;
+
@MockBean
- private UserService service;
+ private UserService userService; //<.>
@Test
- public void givenNotAuthenticated_whenAskingMyDetails_forbidden() throws Exception {
- mvc.perform(get("/api/users/me"))
- .andExpect(status().isUnauthorized());
+ void givenUnauthenticatedUser_userInfoEndpointReturnsUnauthorized() throws Exception {
+ mockMvc.perform(get("/api/users/me"))
+ .andExpect(status().isUnauthorized());
}
@Test
- public void givenAuthenticatedAsOfficer_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.OFFICER_EMAIL, Users.OFFICER_PASSWORD);
-
- when(service.getUser(Users.officer().getId())).thenReturn(Optional.of(Users.officer()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.OFFICER_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"))
- ;
+ void givenAuthenticatedUser_userInfoEndpointReturnsOk() throws Exception {
+ String subject = UUID.randomUUID().toString(); //<.>
+ mockMvc.perform(get("/api/users/me")
+ .with(jwt().jwt(builder -> builder.subject(subject)))) //<.>
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("subject").value(subject)) //<.>
+ .andExpect(jsonPath("claims").isMap());
}
@Test
- public void givenAuthenticatedAsCaptain_whenAskingMyDetails_detailsReturned() throws Exception {
- String accessToken = obtainAccessToken(mvc, Users.CAPTAIN_EMAIL, Users.CAPTAIN_PASSWORD);
-
- when(service.getUser(Users.captain().getId())).thenReturn(Optional.of(Users.captain()));
-
- mvc.perform(get("/api/users/me")
- .header(HEADER_AUTHORIZATION, bearer(accessToken)))
- .andExpect(status().isOk())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(Users.CAPTAIN_EMAIL))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles").value("CAPTAIN"));
+ void givenAuthenticatedOfficer_userIsCreated() throws Exception { //<.>
+ UserId userId = new UserId(UUID.randomUUID());
+ when(userService.createUser(any(CreateUserParameters.class)))
+ .thenReturn(new User(userId,
+ "wim@example.com",
+ new AuthServerId(UUID.fromString("eaa8b8a5-a264-48be-98de-d8b4ae2750ac")),
+ "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"));
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString()))
+ .authorities(new SimpleGrantedAuthority("ROLE_OFFICER")))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("userId").value(userId.asString()))
+ .andExpect(jsonPath("email").value("wim@example.com"))
+ .andExpect(jsonPath("authServerId").value("eaa8b8a5-a264-48be-98de-d8b4ae2750ac"));
}
@Test
- public void testCreateOfficer() throws Exception {
- String email = "wim.deblauwe@example.com";
- String password = "my-super-secret-pwd";
-
- CreateOfficerParameters parameters = new CreateOfficerParameters();
- parameters.setEmail(email);
- parameters.setPassword(password);
-
- when(service.createOfficer(email, password)).thenReturn(Users.newOfficer(email, password));
-
- mvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON_UTF8)
- .content(objectMapper.writeValueAsString(parameters)))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("id").exists())
- .andExpect(jsonPath("email").value(email))
- .andExpect(jsonPath("roles").isArray())
- .andExpect(jsonPath("roles[0]").value("OFFICER"));
-
- verify(service).createOfficer(email, password);
+ void givenAuthenticatedUserThatIsNotAnOfficer_forbiddenIsReturned() throws Exception {
+ mockMvc.perform(post("/api/users")
+ .with(jwt().jwt(builder -> builder.subject(UUID.randomUUID().toString())))
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("""
+ {
+ "mobileToken": "c41536a5a8b9d3f14a7e5472a5322b5e1f76a6e7a9255c2c2e7e0d3a2c5b9d0"
+ }
+ """))
+ .andExpect(status().isForbidden()); // <.>
}
}
diff --git a/chapter07/02 - testcontainers/src/test/resources/application-integration-test.properties b/chapter07/02 - testcontainers/src/test/resources/application-integration-test.properties
index 159536c..c61e563 100644
--- a/chapter07/02 - testcontainers/src/test/resources/application-integration-test.properties
+++ b/chapter07/02 - testcontainers/src/test/resources/application-integration-test.properties
@@ -1,11 +1,6 @@
-spring.datasource.url=jdbc:tc:postgresql://localhost/copsbootdb
+spring.datasource.url=jdbc:tc:postgresql:16://localhost/copsbootdb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.username=user
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
-spring.jpa.hibernate.ddl-auto=none
-
-copsboot-security.mobile-app-client-id=test-client-id
-copsboot-security.mobile-app-client-secret=test-client-secret
-
-spring.flyway.locations=classpath:db/migration/postgresql
\ No newline at end of file
+spring.jpa.hibernate.ddl-auto=validate
diff --git a/chapter07/02 - testcontainers/src/test/resources/application-repository-test.properties b/chapter07/02 - testcontainers/src/test/resources/application-repository-test.properties
new file mode 100644
index 0000000..c61e563
--- /dev/null
+++ b/chapter07/02 - testcontainers/src/test/resources/application-repository-test.properties
@@ -0,0 +1,6 @@
+spring.datasource.url=jdbc:tc:postgresql:16://localhost/copsbootdb
+spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
+spring.datasource.username=user
+spring.datasource.password=password
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.hibernate.ddl-auto=validate
diff --git a/chapter07/02 - testcontainers/src/test/resources/application-test.properties b/chapter07/02 - testcontainers/src/test/resources/application-test.properties
deleted file mode 100644
index 02b4003..0000000
--- a/chapter07/02 - testcontainers/src/test/resources/application-test.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-copsboot-security.mobile-app-client-id=test-client-id
-copsboot-security.mobile-app-client-secret=test-client-secret
-
-spring.flyway.locations=classpath:db/migration/h2
-spring.jpa.hibernate.ddl-auto=create-drop
\ No newline at end of file
diff --git a/chapter07/02 - testcontainers/src/test/resources/logback-test.xml b/chapter07/02 - testcontainers/src/test/resources/logback-test.xml
index bf47fec..164429c 100644
--- a/chapter07/02 - testcontainers/src/test/resources/logback-test.xml
+++ b/chapter07/02 - testcontainers/src/test/resources/logback-test.xml
@@ -5,7 +5,7 @@
-
+
@@ -17,14 +17,8 @@
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.jar b/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.properties b/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..2e76e18
--- /dev/null
+++ b/chapter08/01 - builtin/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/chapter08/01 - builtin/docker-compose.yaml b/chapter08/01 - builtin/docker-compose.yaml
new file mode 100644
index 0000000..92cea56
--- /dev/null
+++ b/chapter08/01 - builtin/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: '3'
+services:
+ db:
+ image: 'postgres:16.0'
+ ports:
+ - 5432:5432
+ environment:
+ POSTGRES_PASSWORD: my-postgres-db-pwd
+ identity:
+ image: 'quay.io/keycloak/keycloak:22.0.1'
+ entrypoint: /opt/keycloak/bin/kc.sh start-dev --import-realm
+ ports:
+ - '8180:8080'
+ environment:
+ KEYCLOAK_LOGLEVEL: 'INFO'
+ KEYCLOAK_ADMIN: 'admin'
+ KEYCLOAK_ADMIN_PASSWORD: 'admin-secret'
+ KC_HOSTNAME: 'localhost'
+ KC_HEALTH_ENABLED: 'true'
+ KC_METRICS_ENABLED: 'true'
diff --git a/chapter08/01 - builtin/mvnw b/chapter08/01 - builtin/mvnw
index 5bf251c..66df285 100755
--- a/chapter08/01 - builtin/mvnw
+++ b/chapter08/01 - builtin/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
+# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@@ -27,7 +27,6 @@
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -36,6 +35,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@@ -50,7 +53,7 @@ fi
cygwin=false;
darwin=false;
mingw=false
-case "`uname`" in
+case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
@@ -58,9 +61,9 @@ case "`uname`" in
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
- export JAVA_HOME="/Library/Java/Home"
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
@@ -68,69 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ JAVA_HOME=$(java-config --jre-home)
fi
fi
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
-
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
-
- saveddir=`pwd`
-
- M2_HOME=`dirname "$PRG"`/..
-
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
-
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
-
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
-# For Migwn, ensure paths are in UNIX format before anything is touched
+# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@@ -146,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
- JAVACMD="`which java`"
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@@ -160,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
-
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
@@ -181,45 +150,159 @@ find_maven_basedir() {
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
- echo "${basedir}"
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
fi
}
-BASE_DIR=`find_maven_basedir "$(pwd)"`
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-echo $MAVEN_PROJECTBASEDIR
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/chapter08/01 - builtin/mvnw.cmd b/chapter08/01 - builtin/mvnw.cmd
index 019bd74..95ba6f5 100644
--- a/chapter08/01 - builtin/mvnw.cmd
+++ b/chapter08/01 - builtin/mvnw.cmd
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,15 +18,14 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -35,7 +34,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -44,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
@@ -115,11 +116,72 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@@ -129,15 +191,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-exit /B %ERROR_CODE%
+cmd /C exit /B %ERROR_CODE%
diff --git a/chapter08/01 - builtin/pom.xml b/chapter08/01 - builtin/pom.xml
index 059f9dd..43db322 100644
--- a/chapter08/01 - builtin/pom.xml
+++ b/chapter08/01 - builtin/pom.xml
@@ -1,231 +1,208 @@
-
- 4.0.0
+
+ 4.0.0
+
+