From 2296ff435ea2544abb06c3b16ab511a4c8e54ffc Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 12:17:16 +0100 Subject: [PATCH 01/30] implementation of Spring security Config --- pom.xml | 4 + .../sfg/brewery/config/SecurityConfig.java | 26 ++++++ src/main/resources/application.properties | 5 +- .../sfg/brewery/web/controllers/BaseIT.java | 48 +++++++++++ .../web/controllers/BeerControllerIT.java | 80 +++++++++++++++++++ .../web/controllers/IndexControllerIT.java | 20 +++++ .../controllers/api/BeerRestControllerIT.java | 33 ++++++++ 7 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/main/java/guru/sfg/brewery/config/SecurityConfig.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java diff --git a/pom.xml b/pom.xml index df7a6cb36..4be087fcc 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-security + org.springframework.security spring-security-test diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java new file mode 100644 index 000000000..f1063f5ae --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -0,0 +1,26 @@ +package guru.sfg.brewery.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests(authorize -> { + authorize.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() + .antMatchers("/beers/find", "/beers*").permitAll() + .antMatchers(HttpMethod.GET, "/api/v1/beer/**", "/api/v1/beerUpc/**").permitAll(); + }) + .authorizeRequests() + .anyRequest() + .authenticated().and() + .formLogin().and() + .httpBasic(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 912080aaa..f3ced6867 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -23,4 +23,7 @@ spring.messages.basename=messages/messages logging.level.guru=debug # Spring Data hangs when not set under Spring Boot 2.3.0 -spring.data.jpa.repositories.bootstrap-mode=default \ No newline at end of file +spring.data.jpa.repositories.bootstrap-mode=default + +spring.security.user.name=spring +spring.security.user.password=password \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java new file mode 100644 index 000000000..f5000e7bc --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java @@ -0,0 +1,48 @@ +package guru.sfg.brewery.web.controllers; + +import guru.sfg.brewery.repositories.BeerInventoryRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.services.BeerService; +import guru.sfg.brewery.services.BreweryService; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + +/** + * Created by jt on 6/13/20. + */ +public abstract class BaseIT { + @Autowired + WebApplicationContext wac; + + protected MockMvc mockMvc; + + @MockBean + BeerRepository beerRepository; + + @MockBean + BeerInventoryRepository beerInventoryRepository; + + @MockBean + BreweryService breweryService; + + @MockBean + CustomerRepository customerRepository; + + @MockBean + BeerService beerService; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(wac) + .apply(springSecurity()) + .build(); + } +} diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java new file mode 100644 index 000000000..436e30710 --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -0,0 +1,80 @@ +package guru.sfg.brewery.web.controllers; + +import guru.sfg.brewery.repositories.BeerInventoryRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.services.BeerService; +import guru.sfg.brewery.services.BreweryService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Created by jt on 6/12/20. + */ +@WebMvcTest +public class BeerControllerIT { + + @Autowired + WebApplicationContext wac; + + MockMvc mockMvc; + + @MockBean + BeerRepository beerRepository; + + @MockBean + BeerInventoryRepository beerInventoryRepository; + + @MockBean + BreweryService breweryService; + + @MockBean + CustomerRepository customerRepository; + + @MockBean + BeerService beerService; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders + .webAppContextSetup(wac) + .apply(springSecurity()) + .build(); + } + + @WithMockUser("spring") + @Test + void findBeers() throws Exception{ + mockMvc.perform(get("/beers/find")) + .andExpect(status().isOk()) + .andExpect(view().name("beers/findBeers")) + .andExpect(model().attributeExists("beer")); + } + + @Test + void findBeersWithHttpBasicFalse() throws Exception{ + mockMvc.perform(get("/beers/find").with(httpBasic("foo", "bar"))) + .andExpect(status().isUnauthorized()); + } + + + @Test + void findBeersWithHttpBasic() throws Exception{ + mockMvc.perform(get("/beers/find").with(httpBasic("spring", "password"))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/findBeers")) + .andExpect(model().attributeExists("beer")); + } +} \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java new file mode 100644 index 000000000..d7d0735d7 --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java @@ -0,0 +1,20 @@ +package guru.sfg.brewery.web.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Created by jt on 6/13/20. + */ +@WebMvcTest +public class IndexControllerIT extends BaseIT { + + @Test + void testGetIndexSlash() throws Exception{ + mockMvc.perform(get("/" )) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java new file mode 100644 index 000000000..703dc8b0b --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -0,0 +1,33 @@ +package guru.sfg.brewery.web.controllers.api; + +import guru.sfg.brewery.web.controllers.BaseIT; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Created by jt on 6/13/20. + */ +@WebMvcTest +public class BeerRestControllerIT extends BaseIT { + + @Test + void findBeers() throws Exception{ + mockMvc.perform(get("/api/v1/beer/")) + .andExpect(status().isOk()); + } + + @Test + void findBeerById() throws Exception{ + mockMvc.perform(get("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311")) + .andExpect(status().isOk()); + } + + @Test + void findBeerByUPC() throws Exception{ + mockMvc.perform(get("/api/v1/beerUpc/0631234200036")) + .andExpect(status().isOk()); + } +} \ No newline at end of file From 3f9676a6c2eb2a8d8e85becdd537facbca304c82 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 13:15:30 +0100 Subject: [PATCH 02/30] adding UserDetailsService --- .../sfg/brewery/config/SecurityConfig.java | 23 +++++++ .../web/controllers/BeerControllerIT.java | 64 ++++--------------- 2 files changed, 37 insertions(+), 50 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index f1063f5ae..1fa997bdb 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,10 +1,15 @@ package guru.sfg.brewery.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @EnableWebSecurity @@ -23,4 +28,22 @@ protected void configure(HttpSecurity http) throws Exception { .formLogin().and() .httpBasic(); } + + @Override + @Bean + protected UserDetailsService userDetailsService() { + UserDetails admin = User.withDefaultPasswordEncoder() + .username("spring") + .password("guru") + .roles("ADMIN") + .build(); + + UserDetails user = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build(); + + return new InMemoryUserDetailsManager(admin, user); + } } diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java index 436e30710..a5c7ad530 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -1,22 +1,10 @@ package guru.sfg.brewery.web.controllers; -import guru.sfg.brewery.repositories.BeerInventoryRepository; -import guru.sfg.brewery.repositories.BeerRepository; -import guru.sfg.brewery.repositories.CustomerRepository; -import guru.sfg.brewery.services.BeerService; -import guru.sfg.brewery.services.BreweryService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -24,37 +12,16 @@ * Created by jt on 6/12/20. */ @WebMvcTest -public class BeerControllerIT { +public class BeerControllerIT extends BaseIT{ - @Autowired - WebApplicationContext wac; - - MockMvc mockMvc; - - @MockBean - BeerRepository beerRepository; - - @MockBean - BeerInventoryRepository beerInventoryRepository; - - @MockBean - BreweryService breweryService; - - @MockBean - CustomerRepository customerRepository; - - @MockBean - BeerService beerService; - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders - .webAppContextSetup(wac) - .apply(springSecurity()) - .build(); + @Test + void initCreationForm() throws Exception { + mockMvc.perform(get("/beers/new").with(httpBasic("user", "password"))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/createBeer")) + .andExpect(model().attributeExists("beer")); } - @WithMockUser("spring") @Test void findBeers() throws Exception{ mockMvc.perform(get("/beers/find")) @@ -64,17 +31,14 @@ void findBeers() throws Exception{ } @Test - void findBeersWithHttpBasicFalse() throws Exception{ - mockMvc.perform(get("/beers/find").with(httpBasic("foo", "bar"))) - .andExpect(status().isUnauthorized()); - } - - - @Test - void findBeersWithHttpBasic() throws Exception{ - mockMvc.perform(get("/beers/find").with(httpBasic("spring", "password"))) + void findBeersWithAnonymous() throws Exception{ + mockMvc.perform(get("/beers/find").with(anonymous())) .andExpect(status().isOk()) .andExpect(view().name("beers/findBeers")) .andExpect(model().attributeExists("beer")); } + + + + } \ No newline at end of file From 9d2f3e0eac1b5cc2ab729d303e02084605c8b085 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 13:16:09 +0100 Subject: [PATCH 03/30] comment out property of defoult user of spring security --- src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3ced6867..d22f7dcd4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,5 +25,5 @@ logging.level.guru=debug # Spring Data hangs when not set under Spring Boot 2.3.0 spring.data.jpa.repositories.bootstrap-mode=default -spring.security.user.name=spring -spring.security.user.password=password \ No newline at end of file +#spring.security.user.name=spring +#spring.security.user.password=password \ No newline at end of file From b5fcb4939194ccb41931bc1267c9a7dc7af03710 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 13:20:53 +0100 Subject: [PATCH 04/30] added new user with customer role --- src/main/java/guru/sfg/brewery/config/SecurityConfig.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 1fa997bdb..67833d9d1 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -44,6 +44,12 @@ protected UserDetailsService userDetailsService() { .roles("USER") .build(); - return new InMemoryUserDetailsManager(admin, user); + UserDetails customer = User.withDefaultPasswordEncoder() + .username("scott") + .password("tiger") + .roles("CUSTOMER") + .build(); + + return new InMemoryUserDetailsManager(admin, user, customer); } } From be1257db276e54b76521ff7f81a694b75f28c906 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 13:47:28 +0100 Subject: [PATCH 05/30] added new user with customer role --- .../sfg/brewery/web/controllers/api/BeerRestControllerIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index 703dc8b0b..580d98fcf 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -17,6 +17,7 @@ public class BeerRestControllerIT extends BaseIT { void findBeers() throws Exception{ mockMvc.perform(get("/api/v1/beer/")) .andExpect(status().isOk()); + } @Test From 3b22bc2c3a591bf3660671a09d6da05c9812b612 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 7 Jan 2021 15:46:03 +0100 Subject: [PATCH 06/30] PasswordEncodingTests --- .../sfg/brewery/config/SecurityConfig.java | 56 +++++++++++++------ .../web/controllers/BeerControllerIT.java | 2 +- .../controllers/PasswordEncodingTests.java | 30 ++++++++++ 3 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 67833d9d1..291ce67a1 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -3,12 +3,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @@ -29,27 +32,46 @@ protected void configure(HttpSecurity http) throws Exception { .httpBasic(); } +// @Override +// @Bean +// protected UserDetailsService userDetailsService() { +// UserDetails admin = User.withDefaultPasswordEncoder() +// .username("spring") +// .password("password") +// .roles("ADMIN") +// .build(); +// +// UserDetails user = User.withDefaultPasswordEncoder() +// .username("user") +// .password("password") +// .roles("USER") +// .build(); +// +// UserDetails customer = User.withDefaultPasswordEncoder() +// .username("scott") +// .password("tiger") +// .roles("CUSTOMER") +// .build(); +// +// return new InMemoryUserDetailsManager(admin, user, customer); +// } + @Override - @Bean - protected UserDetailsService userDetailsService() { - UserDetails admin = User.withDefaultPasswordEncoder() - .username("spring") - .password("guru") + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("spring") + .password("password") .roles("ADMIN") - .build(); - - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") + .and() + .withUser("user") .password("password") - .roles("USER") - .build(); + .roles("USER"); - UserDetails customer = User.withDefaultPasswordEncoder() - .username("scott") - .password("tiger") - .roles("CUSTOMER") - .build(); + auth.inMemoryAuthentication().withUser("scott").password("tiger").roles("CUSTOMER"); + } - return new InMemoryUserDetailsManager(admin, user, customer); + @Bean + PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); } } diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java index a5c7ad530..6b6bf7edb 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -16,7 +16,7 @@ public class BeerControllerIT extends BaseIT{ @Test void initCreationForm() throws Exception { - mockMvc.perform(get("/beers/new").with(httpBasic("user", "password"))) + mockMvc.perform(get("/beers/new").with(httpBasic("spring", "password"))) .andExpect(status().isOk()) .andExpect(view().name("beers/createBeer")) .andExpect(model().attributeExists("beer")); diff --git a/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java b/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java new file mode 100644 index 000000000..928e21dbb --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java @@ -0,0 +1,30 @@ +package guru.sfg.brewery.web.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.DigestUtils; + +/** + * Created by jt on 6/16/20. + */ +public class PasswordEncodingTests { + + static final String PASSWORD = "password"; + + @Test + void testNoOp() { + PasswordEncoder noOp = NoOpPasswordEncoder.getInstance(); + + System.out.println(noOp.encode(PASSWORD)); + } + + @Test + void hashingExample() { + System.out.println(DigestUtils.md5DigestAsHex(PASSWORD.getBytes())); + System.out.println(DigestUtils.md5DigestAsHex(PASSWORD.getBytes())); + + String salted = PASSWORD + "ThisIsMySALTVALUE"; + System.out.println(DigestUtils.md5DigestAsHex(salted.getBytes())); + } +} \ No newline at end of file From ebc8ed1500c8b703ab7674336d12d455865cac4a Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Fri, 8 Jan 2021 10:19:13 +0100 Subject: [PATCH 07/30] adding Auth filter --- .../brewery/config/RestHeaderAuthFilter.java | 52 +++++++++++++++++++ .../sfg/brewery/config/SecurityConfig.java | 16 ++++-- .../controllers/api/BeerRestControllerIT.java | 9 ++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java diff --git a/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java new file mode 100644 index 000000000..ad8dc3fb3 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java @@ -0,0 +1,52 @@ +package guru.sfg.brewery.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by jt on 6/19/20. + */ +@Slf4j +public class RestHeaderAuthFilter extends AbstractAuthenticationProcessingFilter { + + public RestHeaderAuthFilter(RequestMatcher requiresAuthenticationRequestMatcher) { + super(requiresAuthenticationRequestMatcher); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + String userName = getUsername(request); + String password = getPassword(request); + + if (userName == null){ + userName = ""; + } + + if (password == null){ + password = ""; + } + + log.debug("Authenticating User: " + userName); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password); + + return this.getAuthenticationManager().authenticate(token); + } + + private String getPassword(HttpServletRequest request) { + return request.getHeader("Api-Secret"); + } + + private String getUsername(HttpServletRequest request) { + return request.getHeader("Api-Key"); + } +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 291ce67a1..f181b5378 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -3,23 +3,31 @@ 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.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { + public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager){ + RestHeaderAuthFilter filter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**")); + filter.setAuthenticationManager(authenticationManager); + return filter; + } + @Override protected void configure(HttpSecurity http) throws Exception { + http.addFilterBefore(restHeaderAuthFilter(authenticationManager()), + UsernamePasswordAuthenticationFilter.class); + http.authorizeRequests(authorize -> { authorize.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() .antMatchers("/beers/find", "/beers*").permitAll() diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index 580d98fcf..b308d22a0 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -13,6 +14,14 @@ @WebMvcTest public class BeerRestControllerIT extends BaseIT { + + @Test + void deleteBeer() throws Exception { + mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") + .header("Api-Key", "spring").header("Api-Secret", "guru")) + .andExpect(status().isOk()); + } + @Test void findBeers() throws Exception{ mockMvc.perform(get("/api/v1/beer/")) From 157321e27498028d17f47a728538cdb643b25551 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Fri, 8 Jan 2021 10:49:03 +0100 Subject: [PATCH 08/30] adding Auth filter --- .../brewery/config/RestHeaderAuthFilter.java | 74 ++++++++++++++++++- .../sfg/brewery/config/SecurityConfig.java | 12 +-- .../config/SfgPasswordEncoderFactories.java | 30 ++++++++ src/main/resources/application.properties | 5 +- .../controllers/api/BeerRestControllerIT.java | 27 ++++++- 5 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/config/SfgPasswordEncoderFactories.java diff --git a/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java index ad8dc3fb3..5194dee0e 100644 --- a/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java +++ b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java @@ -1,13 +1,19 @@ package guru.sfg.brewery.config; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.StringUtils; +import javax.servlet.FilterChain; import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -22,16 +28,44 @@ public RestHeaderAuthFilter(RequestMatcher requiresAuthenticationRequestMatcher) super(requiresAuthenticationRequestMatcher); } + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + + if (logger.isDebugEnabled()) { + logger.debug("Request is to process authentication"); + } + + Authentication authResult = null; + try { + authResult = attemptAuthentication(request, response); + if (authResult != null) { + successfulAuthentication(request, response, chain, authResult); + } else { + chain.doFilter(request, response); + } + } catch (AuthenticationException exception) { + log.error("Authentication failed" + exception); + unsuccessfulAuthentication(request, response, exception); + } + + + } + @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String userName = getUsername(request); String password = getPassword(request); - if (userName == null){ + if (userName == null) { userName = ""; } - if (password == null){ + if (password == null) { password = ""; } @@ -39,7 +73,41 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password); - return this.getAuthenticationManager().authenticate(token); + if (!StringUtils.isEmpty(userName)) { + return this.getAuthenticationManager().authenticate(token); + } else { + return null; + } + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, + HttpServletResponse response, AuthenticationException failed) + throws IOException, ServletException { + + SecurityContextHolder.clearContext(); + + if (log.isDebugEnabled()) { + log.debug("Authentication request failed: " + failed.toString(), failed); + log.debug("Updated SecurityContextHolder to contain null Authentication"); + } + + response.sendError(HttpStatus.UNAUTHORIZED.value(), + HttpStatus.UNAUTHORIZED.getReasonPhrase()); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, + HttpServletResponse response, FilterChain chain, Authentication authResult) + throws IOException, ServletException { + + if (logger.isDebugEnabled()) { + logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + + authResult); + } + + SecurityContextHolder.getContext().setAuthentication(authResult); + } private String getPassword(HttpServletRequest request) { diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index f181b5378..7c0723a4b 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -38,6 +38,8 @@ protected void configure(HttpSecurity http) throws Exception { .authenticated().and() .formLogin().and() .httpBasic(); + + http.csrf().disable(); } // @Override @@ -68,18 +70,18 @@ protected void configure(HttpSecurity http) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("spring") - .password("password") + .password("{bcrypt}$2a$10$7tYAvVL2/KwcQTcQywHIleKueg4ZK7y7d44hKyngjTwHCDlesxdla") .roles("ADMIN") .and() .withUser("user") - .password("password") + .password("{sha256}1296cefceb47413d3fb91ac7586a4625c33937b4d3109f5a4dd96c79c46193a029db713b96006ded") .roles("USER"); - auth.inMemoryAuthentication().withUser("scott").password("tiger").roles("CUSTOMER"); + auth.inMemoryAuthentication().withUser("scott").password("{bcrypt10}$2a$10$jv7rEbL65k4Q3d/mqG5MLuLDLTlg5oKoq2QOOojfB3e2awo.nlmgu").roles("CUSTOMER"); } @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); + PasswordEncoder passwordEncoder(){ + return SfgPasswordEncoderFactories.createDelegatingPasswordEncoder(); } } diff --git a/src/main/java/guru/sfg/brewery/config/SfgPasswordEncoderFactories.java b/src/main/java/guru/sfg/brewery/config/SfgPasswordEncoderFactories.java new file mode 100644 index 000000000..5e47fb67a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/SfgPasswordEncoderFactories.java @@ -0,0 +1,30 @@ +package guru.sfg.brewery.config; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by jt on 6/17/20. + */ +public class SfgPasswordEncoderFactories { + + public static PasswordEncoder createDelegatingPasswordEncoder() { + String encodingId = "bcrypt10"; + Map encoders = new HashMap<>(); + encoders.put(encodingId, new BCryptPasswordEncoder(10)); + encoders.put("bcrypt", new BCryptPasswordEncoder()); + encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); + encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); + encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); + + return new DelegatingPasswordEncoder(encodingId, encoders); + } + + //don't instantiate class + private SfgPasswordEncoderFactories() { + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d22f7dcd4..c75513b24 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,4 +26,7 @@ logging.level.guru=debug spring.data.jpa.repositories.bootstrap-mode=default #spring.security.user.name=spring -#spring.security.user.password=password \ No newline at end of file +#spring.security.user.password=password + +logging.level.guru.sfg.brewery=debug +logging.level.org.springframework.security=debug \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index b308d22a0..c04973636 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -23,20 +24,40 @@ void deleteBeer() throws Exception { } @Test - void findBeers() throws Exception{ + void deleteBeerBadCredentials() throws Exception { + mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") + .header("Api-Key", "spring").header("Api-Secret", "passwordInValid")) + .andExpect(status().isUnauthorized()); + } + + @Test + void deleteBeerWithHttpBasic() throws Exception { + mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") + .with(httpBasic("spring", "guru"))) + .andExpect(status().is2xxSuccessful()); + } + + @Test + void deleteBeerNoAuth() throws Exception { + mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311")) + .andExpect(status().isUnauthorized()); + } + + @Test + void findBeers() throws Exception { mockMvc.perform(get("/api/v1/beer/")) .andExpect(status().isOk()); } @Test - void findBeerById() throws Exception{ + void findBeerById() throws Exception { mockMvc.perform(get("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311")) .andExpect(status().isOk()); } @Test - void findBeerByUPC() throws Exception{ + void findBeerByUPC() throws Exception { mockMvc.perform(get("/api/v1/beerUpc/0631234200036")) .andExpect(status().isOk()); } From 44a5130aa24bd1485d6b54452f0e38bab99ae534 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Fri, 8 Jan 2021 20:31:44 +0100 Subject: [PATCH 09/30] implementing user details service --- .../sfg/brewery/bootstrap/UserLoader.java | 63 +++++++++++++++++++ .../sfg/brewery/config/SecurityConfig.java | 40 +++++++----- .../brewery/domain/security/Authority.java | 27 ++++++++ .../sfg/brewery/domain/security/User.java | 45 +++++++++++++ .../security/AuthorityRepository.java | 8 +++ .../repositories/security/UserRepository.java | 11 ++++ .../security/JpaUserDetailsService.java | 45 +++++++++++++ src/main/resources/application.properties | 9 +++ .../web/controllers/BeerControllerIT.java | 5 +- .../controllers/api/BeerRestControllerIT.java | 3 +- 10 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java create mode 100644 src/main/java/guru/sfg/brewery/domain/security/Authority.java create mode 100644 src/main/java/guru/sfg/brewery/domain/security/User.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java create mode 100644 src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java diff --git a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java new file mode 100644 index 000000000..991a6615a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java @@ -0,0 +1,63 @@ +package guru.sfg.brewery.bootstrap; + +import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.AuthorityRepository; +import guru.sfg.brewery.repositories.security.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@AllArgsConstructor +@Component +public class UserLoader implements CommandLineRunner { + + private final PasswordEncoder passwordEncoder; + private final UserRepository userRepository; + private final AuthorityRepository authorityRepository; + + @Override + public void run(String... args) { + loadUserData(); + } + + private void loadUserData() { + + Authority a1 = Authority.builder().role("ADMIN").build(); + Authority a2 = Authority.builder().role("USER").build(); + Authority a3 = Authority.builder().role("CUSTOMER").build(); + + User user1 = User.builder() + .username("spring") + .password(passwordEncoder.encode("guru")) + .authority(a1) + .build(); + + User user2 = User.builder() + .username("user") + .password(passwordEncoder.encode("password")) + .authority(a2) + .build(); + + User user3 = User.builder() + .username("scott") + .password(passwordEncoder.encode("password")) + .authority(a3) + .build(); + + if (authorityRepository.count() == 0) { + authorityRepository.save(a1); + authorityRepository.save(a2); + authorityRepository.save(a3); + } + + + if (userRepository.count() == 0) { + userRepository.save(user1); + userRepository.save(user2); + userRepository.save(user3); + } + + } +} diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 7c0723a4b..aca86e815 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,5 +1,7 @@ package guru.sfg.brewery.config; +import guru.sfg.brewery.services.security.JpaUserDetailsService; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -8,16 +10,18 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { - public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager){ + private final JpaUserDetailsService jpaUserDetailsService; + + public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager) { RestHeaderAuthFilter filter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**")); filter.setAuthenticationManager(authenticationManager); return filter; @@ -29,7 +33,9 @@ protected void configure(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class); http.authorizeRequests(authorize -> { - authorize.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() + authorize + .antMatchers("/h2-console/**").permitAll() + .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() .antMatchers("/beers/find", "/beers*").permitAll() .antMatchers(HttpMethod.GET, "/api/v1/beer/**", "/api/v1/beerUpc/**").permitAll(); }) @@ -40,6 +46,9 @@ protected void configure(HttpSecurity http) throws Exception { .httpBasic(); http.csrf().disable(); + +// h2 console + http.headers().frameOptions().sameOrigin(); } // @Override @@ -68,20 +77,23 @@ protected void configure(HttpSecurity http) throws Exception { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .withUser("spring") - .password("{bcrypt}$2a$10$7tYAvVL2/KwcQTcQywHIleKueg4ZK7y7d44hKyngjTwHCDlesxdla") - .roles("ADMIN") - .and() - .withUser("user") - .password("{sha256}1296cefceb47413d3fb91ac7586a4625c33937b4d3109f5a4dd96c79c46193a029db713b96006ded") - .roles("USER"); - - auth.inMemoryAuthentication().withUser("scott").password("{bcrypt10}$2a$10$jv7rEbL65k4Q3d/mqG5MLuLDLTlg5oKoq2QOOojfB3e2awo.nlmgu").roles("CUSTOMER"); + auth.userDetailsService(this.jpaUserDetailsService).passwordEncoder(passwordEncoder()); } +// auth.inMemoryAuthentication() +// .withUser("spring") +// .password("{bcrypt}$2a$10$7tYAvVL2/KwcQTcQywHIleKueg4ZK7y7d44hKyngjTwHCDlesxdla") +// .roles("ADMIN") +// .and() +// .withUser("user") +// .password("{sha256}1296cefceb47413d3fb91ac7586a4625c33937b4d3109f5a4dd96c79c46193a029db713b96006ded") +// .roles("USER"); +// +// auth.inMemoryAuthentication().withUser("scott").password("{bcrypt10}$2a$10$jv7rEbL65k4Q3d/mqG5MLuLDLTlg5oKoq2QOOojfB3e2awo.nlmgu").roles("CUSTOMER"); +// } + @Bean - PasswordEncoder passwordEncoder(){ + PasswordEncoder passwordEncoder() { return SfgPasswordEncoderFactories.createDelegatingPasswordEncoder(); } } diff --git a/src/main/java/guru/sfg/brewery/domain/security/Authority.java b/src/main/java/guru/sfg/brewery/domain/security/Authority.java new file mode 100644 index 000000000..dd60730f6 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/Authority.java @@ -0,0 +1,27 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +/** + * Created by jt on 6/21/20. + */ +@Entity +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Authority { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private String role; + + @ManyToMany(mappedBy = "authorities") + private Set users; +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java new file mode 100644 index 000000000..ba7305c3c --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -0,0 +1,45 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +/** + * Created by jt on 6/21/20. + */ +@Entity +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private String username; + private String password; + + @Singular + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE) + @JoinTable(name = "user_authority", + joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) + private Set authorities; + + @Builder.Default + private Boolean accountNonExpired = true; + + @Builder.Default + private Boolean accountNonLocked = true; + + @Builder.Default + private Boolean credentialsNonExpired = true; + + @Builder.Default + private Boolean enabled = true; + +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java new file mode 100644 index 000000000..a64272828 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java @@ -0,0 +1,8 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthorityRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java new file mode 100644 index 000000000..5a27e7802 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); +} diff --git a/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java new file mode 100644 index 000000000..48a152f62 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java @@ -0,0 +1,45 @@ +package guru.sfg.brewery.services.security; + +import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class JpaUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username).orElseThrow(() -> { + return new UsernameNotFoundException("User name: " + username + " not found"); + }); + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), + user.getEnabled(), user.getAccountNonExpired(), user.getCredentialsNonExpired(), + user.getAccountNonLocked(), convertToSpringAuthorities(user.getAuthorities())); + } + + private Collection convertToSpringAuthorities(Set authorities) { + if (authorities != null && authorities.size() > 0) { + return authorities.stream() + .map(Authority::getRole) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } else { + return new HashSet<>(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c75513b24..13ff89a5d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,6 +20,15 @@ spring.jackson.serialization.write-date-timestamps-as-nanoseconds=true server.port=8080 spring.messages.basename=messages/messages + #h2 properties +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +hibernate.hbm2ddl.auto=create-drop + + logging.level.guru=debug # Spring Data hangs when not set under Spring Boot 2.3.0 diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java index 6b6bf7edb..f1cf7b4cc 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; @@ -11,12 +12,12 @@ /** * Created by jt on 6/12/20. */ -@WebMvcTest +@SpringBootTest public class BeerControllerIT extends BaseIT{ @Test void initCreationForm() throws Exception { - mockMvc.perform(get("/beers/new").with(httpBasic("spring", "password"))) + mockMvc.perform(get("/beers/new").with(httpBasic("spring", "guru"))) .andExpect(status().isOk()) .andExpect(view().name("beers/createBeer")) .andExpect(model().attributeExists("beer")); diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index c04973636..e700f864f 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -3,6 +3,7 @@ import guru.sfg.brewery.web.controllers.BaseIT; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -12,7 +13,7 @@ /** * Created by jt on 6/13/20. */ -@WebMvcTest +@SpringBootTest public class BeerRestControllerIT extends BaseIT { From 17190ee982eccc13d0cbe7911d7bf06e1b66a641 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Sat, 9 Jan 2021 15:42:53 +0100 Subject: [PATCH 10/30] implementing roles authorities --- .../sfg/brewery/bootstrap/UserLoader.java | 6 +- .../sfg/brewery/config/SecurityConfig.java | 14 +- .../web/controllers/CustomerController.java | 16 ++- .../sfg/brewery/web/controllers/BaseIT.java | 40 +++--- .../web/controllers/BeerControllerIT.java | 102 +++++++++++--- .../web/controllers/BreweryControllerIT.java | 66 +++++++++ .../web/controllers/CustomerControllerIT.java | 80 +++++++++++ .../web/controllers/IndexControllerIT.java | 23 ++- .../controllers/PasswordEncodingTests.java | 49 ++++++- .../controllers/api/BeerRestControllerIT.java | 132 +++++++++++++----- 10 files changed, 437 insertions(+), 91 deletions(-) create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/BreweryControllerIT.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/CustomerControllerIT.java diff --git a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java index 991a6615a..c0fcdeaaa 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java @@ -24,9 +24,9 @@ public void run(String... args) { private void loadUserData() { - Authority a1 = Authority.builder().role("ADMIN").build(); - Authority a2 = Authority.builder().role("USER").build(); - Authority a3 = Authority.builder().role("CUSTOMER").build(); + Authority a1 = Authority.builder().role("ROLE_ADMIN").build(); + Authority a2 = Authority.builder().role("ROLE_USER").build(); + Authority a3 = Authority.builder().role("ROLE_CUSTOMER").build(); User user1 = User.builder() .username("spring") diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index aca86e815..d6dda6807 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -7,6 +7,7 @@ import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -15,8 +16,9 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration -@EnableWebSecurity @RequiredArgsConstructor +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JpaUserDetailsService jpaUserDetailsService; @@ -34,10 +36,14 @@ protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests(authorize -> { authorize - .antMatchers("/h2-console/**").permitAll() + .antMatchers("/h2-console/**").permitAll() //do not use in production! .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() - .antMatchers("/beers/find", "/beers*").permitAll() - .antMatchers(HttpMethod.GET, "/api/v1/beer/**", "/api/v1/beerUpc/**").permitAll(); + .antMatchers("/beers/find", "/beers*").hasAnyRole("ADMIN","CUSTOMER", "USER") + .antMatchers(HttpMethod.GET, "/api/v1/beer/**").permitAll() + .mvcMatchers(HttpMethod.DELETE, "/api/v1/beer/**").hasRole("ADMIN") + .mvcMatchers(HttpMethod.GET, "/api/v1/beerUpc/{upc}").permitAll() + .mvcMatchers("/brewery/breweries").hasAnyRole("ADMIN", "CUSTOMER") + .mvcMatchers(HttpMethod.GET, "/brewery/api/v1/breweries").hasAnyRole("ADMIN", "CUSTOMER"); }) .authorizeRequests() .anyRequest() diff --git a/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java b/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java index 06fef9090..042426380 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java @@ -20,6 +20,8 @@ import guru.sfg.brewery.domain.Customer; import guru.sfg.brewery.repositories.CustomerRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -47,6 +49,7 @@ public String findCustomers(Model model){ return "customers/findCustomers"; } + @Secured({"ROLE_ADMIN", "ROLE_CUSTOMER"}) @GetMapping public String processFindFormReturnMany(Customer customer, BindingResult result, Model model){ // find customers by name @@ -67,7 +70,7 @@ public String processFindFormReturnMany(Customer customer, BindingResult result, } } - @GetMapping("/{customerId}") + @GetMapping("/{customerId}") public ModelAndView showCustomer(@PathVariable UUID customerId) { ModelAndView mav = new ModelAndView("customers/customerDetails"); //ToDO: Add Service @@ -81,6 +84,7 @@ public String initCreationForm(Model model) { return "customers/createCustomer"; } + @PreAuthorize("hasRole('ADMIN')") @PostMapping("/new") public String processCreationForm(Customer customer) { //ToDO: Add Service @@ -93,11 +97,11 @@ public String processCreationForm(Customer customer) { } @GetMapping("/{customerId}/edit") - public String initUpdateCustomerForm(@PathVariable UUID customerId, Model model) { - if(customerRepository.findById(customerId).isPresent()) - model.addAttribute("customer", customerRepository.findById(customerId).get()); - return "customers/createOrUpdateCustomer"; - } + public String initUpdateCustomerForm(@PathVariable UUID customerId, Model model) { + if(customerRepository.findById(customerId).isPresent()) + model.addAttribute("customer", customerRepository.findById(customerId).get()); + return "customers/createOrUpdateCustomer"; + } @PostMapping("/{beerId}/edit") public String processUpdationForm(@Valid Customer customer, BindingResult result) { diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java index f5000e7bc..fc26fdfcb 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BaseIT.java @@ -1,17 +1,14 @@ package guru.sfg.brewery.web.controllers; -import guru.sfg.brewery.repositories.BeerInventoryRepository; -import guru.sfg.brewery.repositories.BeerRepository; -import guru.sfg.brewery.repositories.CustomerRepository; -import guru.sfg.brewery.services.BeerService; -import guru.sfg.brewery.services.BreweryService; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.provider.Arguments; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import java.util.stream.Stream; + import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; /** @@ -23,21 +20,6 @@ public abstract class BaseIT { protected MockMvc mockMvc; - @MockBean - BeerRepository beerRepository; - - @MockBean - BeerInventoryRepository beerInventoryRepository; - - @MockBean - BreweryService breweryService; - - @MockBean - CustomerRepository customerRepository; - - @MockBean - BeerService beerService; - @BeforeEach public void setup() { mockMvc = MockMvcBuilders @@ -45,4 +27,20 @@ public void setup() { .apply(springSecurity()) .build(); } + + public static Stream getStreamAdminCustomer() { + return Stream.of(Arguments.of("spring" , "guru"), + Arguments.of("scott", "password")); + } + + public static Stream getStreamAllUsers() { + return Stream.of(Arguments.of("spring" , "guru"), + Arguments.of("scott", "password"), + Arguments.of("user", "password")); + } + + public static Stream getStreamNotAdmin() { + return Stream.of(Arguments.of("scott", "password"), + Arguments.of("user", "password")); + } } diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java index f1cf7b4cc..94615eec3 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -1,7 +1,13 @@ package guru.sfg.brewery.web.controllers; +import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.repositories.BeerRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; @@ -15,31 +21,89 @@ @SpringBootTest public class BeerControllerIT extends BaseIT{ - @Test - void initCreationForm() throws Exception { - mockMvc.perform(get("/beers/new").with(httpBasic("spring", "guru"))) - .andExpect(status().isOk()) - .andExpect(view().name("beers/createBeer")) - .andExpect(model().attributeExists("beer")); + @Autowired + BeerRepository beerRepository; + + @DisplayName("Init New Form") + @Nested + class InitNewForm{ + + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void initCreationFormAuth(String user, String pwd) throws Exception { + + mockMvc.perform(get("/beers/new").with(httpBasic(user, pwd))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/createBeer")) + .andExpect(model().attributeExists("beer")); + } + + @Test + void initCreationFormNotAuth() throws Exception { + mockMvc.perform(get("/beers/new")) + .andExpect(status().isUnauthorized()); + } } - @Test - void findBeers() throws Exception{ - mockMvc.perform(get("/beers/find")) - .andExpect(status().isOk()) - .andExpect(view().name("beers/findBeers")) - .andExpect(model().attributeExists("beer")); + @DisplayName("Init Find Beer Form") + @Nested + class FindForm{ + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void findBeersFormAUTH(String user, String pwd) throws Exception{ + mockMvc.perform(get("/beers/find") + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/findBeers")) + .andExpect(model().attributeExists("beer")); + } + + @Test + void findBeersWithAnonymous() throws Exception{ + mockMvc.perform(get("/beers/find").with(anonymous())) + .andExpect(status().isUnauthorized()); + } } - @Test - void findBeersWithAnonymous() throws Exception{ - mockMvc.perform(get("/beers/find").with(anonymous())) - .andExpect(status().isOk()) - .andExpect(view().name("beers/findBeers")) - .andExpect(model().attributeExists("beer")); + @DisplayName("Process Find Beer Form") + @Nested + class ProcessFindForm{ + @Test + void findBeerForm() throws Exception { + mockMvc.perform(get("/beers").param("beerName", "")) + .andExpect(status().isUnauthorized()); + } + + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void findBeerFormAuth(String user, String pwd) throws Exception { + mockMvc.perform(get("/beers").param("beerName", "") + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()); + } } + @DisplayName("Get Beer By Id") + @Nested + class GetByID { + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void getBeerByIdAUTH(String user, String pwd) throws Exception{ + Beer beer = beerRepository.findAll().get(0); + mockMvc.perform(get("/beers/" + beer.getId()) + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/beerDetails")) + .andExpect(model().attributeExists("beer")); + } + @Test + void getBeerByIdNoAuth() throws Exception{ + Beer beer = beerRepository.findAll().get(0); + mockMvc.perform(get("/beers/" + beer.getId())) + .andExpect(status().isUnauthorized()); + } + } } \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BreweryControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BreweryControllerIT.java new file mode 100644 index 000000000..878a881ac --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/BreweryControllerIT.java @@ -0,0 +1,66 @@ +package guru.sfg.brewery.web.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +class BreweryControllerIT extends BaseIT { + + @Test + void listBreweriesCUSTOMER() throws Exception { + mockMvc.perform(get("/brewery/breweries") + .with(httpBasic("scott", "tiger"))) + .andExpect(status().is2xxSuccessful()); + } + + @Test + void listBreweriesADMIN() throws Exception { + mockMvc.perform(get("/brewery/breweries") + .with(httpBasic("spring", "guru"))) + .andExpect(status().is2xxSuccessful()); + } + + @Test + void listBreweriesUSER() throws Exception { + mockMvc.perform(get("/brewery/breweries") + .with(httpBasic("user", "password"))) + .andExpect(status().isForbidden()); + } + + @Test + void listBreweriesNOAUTH() throws Exception { + mockMvc.perform(get("/brewery/breweries")) + .andExpect(status().isUnauthorized()); + } + + @Test + void getBreweriesJsonCUSTOMER() throws Exception { + mockMvc.perform(get("/brewery/api/v1/breweries") + .with(httpBasic("scott", "tiger"))) + .andExpect(status().is2xxSuccessful()); + } + + @Test + void getBreweriesJsonADMIN() throws Exception { + mockMvc.perform(get("/brewery/api/v1/breweries") + .with(httpBasic("spring", "guru"))) + .andExpect(status().is2xxSuccessful()); + } + + @Test + void getBreweriesJsonUSER() throws Exception { + mockMvc.perform(get("/brewery/api/v1/breweries") + .with(httpBasic("user", "password"))) + .andExpect(status().isForbidden()); + } + + @Test + void getBreweriesJsonNOAUTH() throws Exception { + mockMvc.perform(get("/brewery/api/v1/breweries")) + .andExpect(status().isUnauthorized()); + } +} \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/CustomerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/CustomerControllerIT.java new file mode 100644 index 000000000..57100313e --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/CustomerControllerIT.java @@ -0,0 +1,80 @@ +package guru.sfg.brewery.web.controllers; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +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.status; + +/** + * Created by jt on 6/27/20. + */ +@SpringBootTest +public class CustomerControllerIT extends BaseIT { + + @DisplayName("List Customers") + @Nested + class ListCustomers{ + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAdminCustomer") + void testListCustomersAUTH(String user, String pwd) throws Exception { + mockMvc.perform(get("/customers") + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()); + + } + + @Test + void testListCustomersNOTAUTH() throws Exception { + mockMvc.perform(get("/customers") + .with(httpBasic("user", "password"))) + .andExpect(status().isForbidden()); + } + + @Test + void testListCustomersNOTLOGGEDIN() throws Exception { + mockMvc.perform(get("/customers")) + .andExpect(status().isUnauthorized()); + + } + } + + @DisplayName("Add Customers") + @Nested + class AddCustomers { + + @Rollback + @Test + void processCreationForm() throws Exception{ + mockMvc.perform(post("/customers/new") + .param("customerName", "Foo Customer") + .with(httpBasic("spring", "guru"))) + .andExpect(status().is3xxRedirection()); + } + + @Rollback + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamNotAdmin") + void processCreationFormNOTAUTH(String user, String pwd) throws Exception{ + mockMvc.perform(post("/customers/new") + .param("customerName", "Foo Customer2") + .with(httpBasic(user, pwd))) + .andExpect(status().isForbidden()); + } + + @Test + void processCreationFormNOAUTH() throws Exception{ + mockMvc.perform(post("/customers/new") + .param("customerName", "Foo Customer")) + .andExpect(status().isUnauthorized()); + } + } + +} \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java index d7d0735d7..34a60b439 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/IndexControllerIT.java @@ -1,7 +1,13 @@ package guru.sfg.brewery.web.controllers; +import guru.sfg.brewery.repositories.BeerInventoryRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.services.BeerService; +import guru.sfg.brewery.services.BreweryService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -12,9 +18,24 @@ @WebMvcTest public class IndexControllerIT extends BaseIT { + @MockBean + BeerRepository beerRepository; + + @MockBean + BeerInventoryRepository beerInventoryRepository; + + @MockBean + BreweryService breweryService; + + @MockBean + CustomerRepository customerRepository; + + @MockBean + BeerService beerService; + @Test void testGetIndexSlash() throws Exception{ mockMvc.perform(get("/" )) .andExpect(status().isOk()); } -} \ No newline at end of file +} diff --git a/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java b/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java index 928e21dbb..0bce4686b 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/PasswordEncodingTests.java @@ -1,17 +1,64 @@ package guru.sfg.brewery.web.controllers; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.LdapShaPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.StandardPasswordEncoder; import org.springframework.util.DigestUtils; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * Created by jt on 6/16/20. */ +@Disabled public class PasswordEncodingTests { static final String PASSWORD = "password"; + @Test + void testBcrypt15() { + PasswordEncoder bcrypt = new BCryptPasswordEncoder(15); + + System.out.println(bcrypt.encode(PASSWORD)); + System.out.println(bcrypt.encode(PASSWORD)); + System.out.println(bcrypt.encode("tiger")); + + } + + @Test + void testBcrypt() { + PasswordEncoder bcrypt = new BCryptPasswordEncoder(); + + System.out.println(bcrypt.encode(PASSWORD)); + System.out.println(bcrypt.encode(PASSWORD)); + System.out.println(bcrypt.encode("guru")); + + } + + @Test + void testSha256() { + PasswordEncoder sha256 = new StandardPasswordEncoder(); + + System.out.println(sha256.encode(PASSWORD)); + System.out.println(sha256.encode(PASSWORD)); + } + + @Test + void testLdap() { + PasswordEncoder ldap = new LdapShaPasswordEncoder(); + System.out.println(ldap.encode(PASSWORD)); + System.out.println(ldap.encode(PASSWORD)); + System.out.println(ldap.encode("tiger")); + String encodedPwd = ldap.encode(PASSWORD); + + assertTrue(ldap.matches(PASSWORD, encodedPwd )); + + } + @Test void testNoOp() { PasswordEncoder noOp = NoOpPasswordEncoder.getInstance(); @@ -27,4 +74,4 @@ void hashingExample() { String salted = PASSWORD + "ThisIsMySALTVALUE"; System.out.println(DigestUtils.md5DigestAsHex(salted.getBytes())); } -} \ No newline at end of file +} diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index e700f864f..79f66db74 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -1,10 +1,19 @@ package guru.sfg.brewery.web.controllers.api; +import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.repositories.BeerRepository; import guru.sfg.brewery.web.controllers.BaseIT; +import guru.sfg.brewery.web.model.BeerStyleEnum; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.util.Random; + import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -16,50 +25,101 @@ @SpringBootTest public class BeerRestControllerIT extends BaseIT { + @Autowired + BeerRepository beerRepository; - @Test - void deleteBeer() throws Exception { - mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") - .header("Api-Key", "spring").header("Api-Secret", "guru")) - .andExpect(status().isOk()); - } + @DisplayName("Delete Tests") + @Nested + class DeleteTests { - @Test - void deleteBeerBadCredentials() throws Exception { - mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") - .header("Api-Key", "spring").header("Api-Secret", "passwordInValid")) - .andExpect(status().isUnauthorized()); - } + public Beer beerToDelete() { + Random rand = new Random(); - @Test - void deleteBeerWithHttpBasic() throws Exception { - mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311") - .with(httpBasic("spring", "guru"))) - .andExpect(status().is2xxSuccessful()); - } + return beerRepository.saveAndFlush(Beer.builder() + .beerName("Delete Me Beer") + .beerStyle(BeerStyleEnum.IPA) + .minOnHand(12) + .quantityToBrew(200) + .upc(String.valueOf(rand.nextInt(99999999))) + .build()); + } + + @Test + void deleteBeerHttpBasic() throws Exception{ + mockMvc.perform(delete("/api/v1/beer/" + beerToDelete().getId()) + .with(httpBasic("spring", "guru"))) + .andExpect(status().is2xxSuccessful()); + } - @Test - void deleteBeerNoAuth() throws Exception { - mockMvc.perform(delete("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311")) - .andExpect(status().isUnauthorized()); + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamNotAdmin") + void deleteBeerHttpBasicNotAuth(String user, String pwd) throws Exception { + mockMvc.perform(delete("/api/v1/beer/" + beerToDelete().getId()) + .with(httpBasic(user, pwd))) + .andExpect(status().isForbidden()); + } + + @Test + void deleteBeerNoAuth() throws Exception { + mockMvc.perform(delete("/api/v1/beer/" + beerToDelete().getId())) + .andExpect(status().isUnauthorized()); + } } - @Test - void findBeers() throws Exception { - mockMvc.perform(get("/api/v1/beer/")) - .andExpect(status().isOk()); + @DisplayName("List Beers") + @Nested + class ListBeers { + @Test + void findBeers() throws Exception { + mockMvc.perform(get("/api/v1/beer/")) + .andExpect(status().isUnauthorized()); + } + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void findBeersAUTH(String user, String pwd) throws Exception { + mockMvc.perform(get("/api/v1/beer/").with(httpBasic(user, pwd))) + .andExpect(status().isOk()); + } } - @Test - void findBeerById() throws Exception { - mockMvc.perform(get("/api/v1/beer/97df0c39-90c4-4ae0-b663-453e8e19c311")) - .andExpect(status().isOk()); + @DisplayName("Get Beer By ID") + @Nested + class GetBeerById { + @Test + void findBeerById() throws Exception { + Beer beer = beerRepository.findAll().get(0); + + mockMvc.perform(get("/api/v1/beer/" + beer.getId())) + .andExpect(status().isUnauthorized()); + } + + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void findBeerByIdAUTH(String user, String pwd) throws Exception { + Beer beer = beerRepository.findAll().get(0); + + mockMvc.perform(get("/api/v1/beer/" + beer.getId()) + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()); + } } - @Test - void findBeerByUPC() throws Exception { - mockMvc.perform(get("/api/v1/beerUpc/0631234200036")) - .andExpect(status().isOk()); + @Nested + @DisplayName("Find By UPC") + class FindByUPC { + @Test + void findBeerByUpc() throws Exception { + mockMvc.perform(get("/api/v1/beerUpc/0631234200036")) + .andExpect(status().isUnauthorized()); + } + + @ParameterizedTest(name = "#{index} with [{arguments}]") + @MethodSource("guru.sfg.brewery.web.controllers.BeerControllerIT#getStreamAllUsers") + void findBeerByUpcAUTH(String user, String pwd) throws Exception { + mockMvc.perform(get("/api/v1/beerUpc/0631234200036") + .with(httpBasic(user, pwd))) + .andExpect(status().isOk()); + } } -} \ No newline at end of file +} From e7e646773212866ae0ef5134f16093a2b1df5750 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Sat, 9 Jan 2021 17:10:18 +0100 Subject: [PATCH 11/30] implementing roles authorities --- .../sfg/brewery/bootstrap/UserLoader.java | 75 +++++++++++++------ .../sfg/brewery/config/SecurityConfig.java | 30 +++----- .../brewery/domain/security/Authority.java | 4 +- .../sfg/brewery/domain/security/Role.java | 31 ++++++++ .../sfg/brewery/domain/security/User.java | 17 ++++- .../permission/BeerCreatePermission.java | 11 +++ .../permission/BeerDeletePermission.java | 11 +++ .../permission/BeerReadPermission.java | 11 +++ .../permission/BeerUpdatePermission.java | 11 +++ .../security/AuthorityRepository.java | 1 - .../repositories/security/RoleRepository.java | 7 ++ .../security/JpaUserDetailsService.java | 2 +- .../web/controllers/BeerController.java | 10 +++ .../controllers/api/BeerRestController.java | 11 +++ 14 files changed, 182 insertions(+), 50 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/domain/security/Role.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java diff --git a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java index c0fcdeaaa..446a296c1 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java @@ -1,14 +1,23 @@ package guru.sfg.brewery.bootstrap; import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.Role; import guru.sfg.brewery.domain.security.User; import guru.sfg.brewery.repositories.security.AuthorityRepository; +import guru.sfg.brewery.repositories.security.RoleRepository; import guru.sfg.brewery.repositories.security.UserRepository; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import javax.transaction.Transactional; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@Slf4j @AllArgsConstructor @Component public class UserLoader implements CommandLineRunner { @@ -16,48 +25,66 @@ public class UserLoader implements CommandLineRunner { private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; private final AuthorityRepository authorityRepository; + private final RoleRepository roleRepository; @Override + @Transactional public void run(String... args) { loadUserData(); } private void loadUserData() { + //beer auths + Authority createBeer = authorityRepository.save(Authority.builder().permission("beer.create").build()); + Authority readBeer = authorityRepository.save(Authority.builder().permission("beer.read").build()); + Authority updateBeer = authorityRepository.save(Authority.builder().permission("beer.update").build()); + Authority deleteBeer = authorityRepository.save(Authority.builder().permission("beer.delete").build()); + + //customer auths + Authority createCustomer = authorityRepository.save(Authority.builder().permission("customer.create").build()); + Authority readCustomer = authorityRepository.save(Authority.builder().permission("customer.read").build()); + Authority updateCustomer = authorityRepository.save(Authority.builder().permission("customer.update").build()); + Authority deleteCustomer = authorityRepository.save(Authority.builder().permission("customer.delete").build()); + + //customer brewery + Authority createBrewery = authorityRepository.save(Authority.builder().permission("brewery.create").build()); + Authority readBrewery = authorityRepository.save(Authority.builder().permission("brewery.read").build()); + Authority updateBrewery = authorityRepository.save(Authority.builder().permission("brewery.update").build()); + Authority deleteBrewery = authorityRepository.save(Authority.builder().permission("brewery.delete").build()); + + + Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); + Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); + Role userRole = roleRepository.save(Role.builder().name("USER").build()); - Authority a1 = Authority.builder().role("ROLE_ADMIN").build(); - Authority a2 = Authority.builder().role("ROLE_USER").build(); - Authority a3 = Authority.builder().role("ROLE_CUSTOMER").build(); + adminRole.setAuthorities(new HashSet<>(Set.of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, + updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery))); - User user1 = User.builder() + customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery))); + + userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); + + roleRepository.saveAll(Arrays.asList(adminRole, customerRole, userRole)); + + userRepository.save(User.builder() .username("spring") .password(passwordEncoder.encode("guru")) - .authority(a1) - .build(); + .role(adminRole) + .build()); - User user2 = User.builder() + userRepository.save(User.builder() .username("user") .password(passwordEncoder.encode("password")) - .authority(a2) - .build(); + .role(userRole) + .build()); - User user3 = User.builder() + userRepository.save(User.builder() .username("scott") .password(passwordEncoder.encode("password")) - .authority(a3) - .build(); - - if (authorityRepository.count() == 0) { - authorityRepository.save(a1); - authorityRepository.save(a2); - authorityRepository.save(a3); - } - + .role(customerRole) + .build()); - if (userRepository.count() == 0) { - userRepository.save(user1); - userRepository.save(user2); - userRepository.save(user3); - } + log.debug("Users Loaded: " + userRepository.count()); } } diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index d6dda6807..e3f33da51 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -31,29 +31,21 @@ public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authentic @Override protected void configure(HttpSecurity http) throws Exception { - http.addFilterBefore(restHeaderAuthFilter(authenticationManager()), - UsernamePasswordAuthenticationFilter.class); - http.authorizeRequests(authorize -> { - authorize - .antMatchers("/h2-console/**").permitAll() //do not use in production! - .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() - .antMatchers("/beers/find", "/beers*").hasAnyRole("ADMIN","CUSTOMER", "USER") - .antMatchers(HttpMethod.GET, "/api/v1/beer/**").permitAll() - .mvcMatchers(HttpMethod.DELETE, "/api/v1/beer/**").hasRole("ADMIN") - .mvcMatchers(HttpMethod.GET, "/api/v1/beerUpc/{upc}").permitAll() - .mvcMatchers("/brewery/breweries").hasAnyRole("ADMIN", "CUSTOMER") - .mvcMatchers(HttpMethod.GET, "/brewery/api/v1/breweries").hasAnyRole("ADMIN", "CUSTOMER"); - }) + http + .authorizeRequests(authorize -> { + authorize + .antMatchers("/h2-console/**").permitAll() //do not use in production! + .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); + } ) .authorizeRequests() - .anyRequest() - .authenticated().and() + .anyRequest().authenticated() + .and() .formLogin().and() - .httpBasic(); + .httpBasic() + .and().csrf().disable(); - http.csrf().disable(); - -// h2 console + //h2 console config http.headers().frameOptions().sameOrigin(); } diff --git a/src/main/java/guru/sfg/brewery/domain/security/Authority.java b/src/main/java/guru/sfg/brewery/domain/security/Authority.java index dd60730f6..f0ab96d78 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/Authority.java +++ b/src/main/java/guru/sfg/brewery/domain/security/Authority.java @@ -20,8 +20,8 @@ public class Authority { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - private String role; + private String permission; @ManyToMany(mappedBy = "authorities") - private Set users; + private Set roles; } \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/Role.java b/src/main/java/guru/sfg/brewery/domain/security/Role.java new file mode 100644 index 000000000..a862a8e1a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/Role.java @@ -0,0 +1,31 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +@Entity +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private String name; + + @ManyToMany(mappedBy = "roles") + private Set users; + + @Singular + @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER) + @JoinTable(name = "role_authority", + joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) + private Set authorities; +} diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index ba7305c3c..a6bd808be 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -4,6 +4,7 @@ import javax.persistence.*; import java.util.Set; +import java.util.stream.Collectors; /** * Created by jt on 6/21/20. @@ -24,12 +25,22 @@ public class User { private String password; @Singular - @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE) - @JoinTable(name = "user_authority", + @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER) + @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, - inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) + inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) + private Set roles; + + @Transient private Set authorities; + public Set getAuthorities() { + return this.roles.stream() + .map(Role::getAuthorities) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + } + @Builder.Default private Boolean accountNonExpired = true; diff --git a/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java new file mode 100644 index 000000000..69ad9037c --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('beer.create')") +public @interface BeerCreatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java new file mode 100644 index 000000000..9882a7b09 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('beer.delete')") +public @interface BeerDeletePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java b/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java new file mode 100644 index 000000000..07d8ff0d0 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('beer.read')") +public @interface BeerReadPermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java new file mode 100644 index 000000000..7802216f4 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('beer.update')") +public @interface BeerUpdatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java index a64272828..20ddc1565 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java @@ -1,7 +1,6 @@ package guru.sfg.brewery.repositories.security; import guru.sfg.brewery.domain.security.Authority; -import guru.sfg.brewery.domain.security.User; import org.springframework.data.jpa.repository.JpaRepository; public interface AuthorityRepository extends JpaRepository { diff --git a/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java new file mode 100644 index 000000000..2d112aa40 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java @@ -0,0 +1,7 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoleRepository extends JpaRepository { +} diff --git a/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java index 48a152f62..781240fd1 100644 --- a/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java +++ b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java @@ -35,7 +35,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx private Collection convertToSpringAuthorities(Set authorities) { if (authorities != null && authorities.size() > 0) { return authorities.stream() - .map(Authority::getRole) + .map(Authority::getPermission) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } else { diff --git a/src/main/java/guru/sfg/brewery/web/controllers/BeerController.java b/src/main/java/guru/sfg/brewery/web/controllers/BeerController.java index ee5d9cdd5..ef0060f8f 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/BeerController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/BeerController.java @@ -19,6 +19,9 @@ import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.permission.BeerCreatePermission; +import guru.sfg.brewery.permission.BeerReadPermission; +import guru.sfg.brewery.permission.BeerUpdatePermission; import guru.sfg.brewery.repositories.BeerInventoryRepository; import guru.sfg.brewery.repositories.BeerRepository; import lombok.RequiredArgsConstructor; @@ -48,12 +51,14 @@ public class BeerController { private final BeerInventoryRepository beerInventoryRepository; + @BeerReadPermission @RequestMapping("/find") public String findBeers(Model model) { model.addAttribute("beer", Beer.builder().build()); return "beers/findBeers"; } + @BeerReadPermission @GetMapping public String processFindFormReturnMany(Beer beer, BindingResult result, Model model) { // find beers by name @@ -77,6 +82,7 @@ public String processFindFormReturnMany(Beer beer, BindingResult result, Model m } + @BeerReadPermission @GetMapping("/{beerId}") public ModelAndView showBeer(@PathVariable UUID beerId) { ModelAndView mav = new ModelAndView("beers/beerDetails"); @@ -85,12 +91,14 @@ public ModelAndView showBeer(@PathVariable UUID beerId) { return mav; } + @BeerCreatePermission @GetMapping("/new") public String initCreationForm(Model model) { model.addAttribute("beer", Beer.builder().build()); return "beers/createBeer"; } + @BeerCreatePermission @PostMapping("/new") public String processCreationForm(Beer beer) { //ToDO: Add Service @@ -107,6 +115,7 @@ public String processCreationForm(Beer beer) { return "redirect:/beers/" + savedBeer.getId(); } + @BeerUpdatePermission @GetMapping("/{beerId}/edit") public String initUpdateBeerForm(@PathVariable UUID beerId, Model model) { if (beerRepository.findById(beerId).isPresent()) @@ -114,6 +123,7 @@ public String initUpdateBeerForm(@PathVariable UUID beerId, Model model) { return "beers/createOrUpdateBeer"; } + @BeerUpdatePermission @PostMapping("/{beerId}/edit") public String processUpdateForm(@Valid Beer beer, BindingResult result) { if (result.hasErrors()) { diff --git a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java index 057ff9a81..be72399db 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java @@ -17,6 +17,10 @@ package guru.sfg.brewery.web.controllers.api; +import guru.sfg.brewery.permission.BeerCreatePermission; +import guru.sfg.brewery.permission.BeerDeletePermission; +import guru.sfg.brewery.permission.BeerReadPermission; +import guru.sfg.brewery.permission.BeerUpdatePermission; import guru.sfg.brewery.services.BeerService; import guru.sfg.brewery.web.model.BeerDto; import guru.sfg.brewery.web.model.BeerPagedList; @@ -27,6 +31,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.validation.ConstraintViolationException; @@ -46,6 +51,7 @@ public class BeerRestController { private final BeerService beerService; + @BeerReadPermission @GetMapping(produces = { "application/json" }, path = "beer") public ResponseEntity listBeers(@RequestParam(value = "pageNumber", required = false) Integer pageNumber, @RequestParam(value = "pageSize", required = false) Integer pageSize, @@ -72,6 +78,7 @@ public ResponseEntity listBeers(@RequestParam(value = "pageNumber return new ResponseEntity<>(beerList, HttpStatus.OK); } + @BeerReadPermission @GetMapping(path = {"beer/{beerId}"}, produces = { "application/json" }) public ResponseEntity getBeerById(@PathVariable("beerId") UUID beerId, @RequestParam(value = "showInventoryOnHand", required = false) Boolean showInventoryOnHand){ @@ -85,11 +92,13 @@ public ResponseEntity getBeerById(@PathVariable("beerId") UUID beerId, return new ResponseEntity<>(beerService.findBeerById(beerId, showInventoryOnHand), HttpStatus.OK); } + @BeerReadPermission @GetMapping(path = {"beerUpc/{upc}"}, produces = { "application/json" }) public ResponseEntity getBeerByUpc(@PathVariable("upc") String upc){ return new ResponseEntity<>(beerService.findBeerByUpc(upc), HttpStatus.OK); } + @BeerCreatePermission @PostMapping(path = "beer") public ResponseEntity saveNewBeer(@Valid @RequestBody BeerDto beerDto){ @@ -103,6 +112,7 @@ public ResponseEntity saveNewBeer(@Valid @RequestBody BeerDto beerDto){ return new ResponseEntity(httpHeaders, HttpStatus.CREATED); } + @BeerUpdatePermission @PutMapping(path = {"beer/{beerId}"}, produces = { "application/json" }) public ResponseEntity updateBeer(@PathVariable("beerId") UUID beerId, @Valid @RequestBody BeerDto beerDto){ @@ -111,6 +121,7 @@ public ResponseEntity updateBeer(@PathVariable("beerId") UUID beerId, @Valid @Re return new ResponseEntity<>(HttpStatus.NO_CONTENT); } + @BeerDeletePermission @DeleteMapping({"beer/{beerId}"}) @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteBeer(@PathVariable("beerId") UUID beerId){ From 28c088088cd32620b95c90441ca21aeb334ead51 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 20:58:09 +0100 Subject: [PATCH 12/30] added BeerOrderController --- .../controllers/api/BeerOrderController.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java diff --git a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java new file mode 100644 index 000000000..9675bfa2f --- /dev/null +++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java @@ -0,0 +1,60 @@ +package guru.sfg.brewery.web.controllers.api; + +import guru.sfg.brewery.services.BeerOrderService; +import guru.sfg.brewery.web.model.BeerOrderDto; +import guru.sfg.brewery.web.model.BeerOrderPagedList; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +/** + * Beer Order Controller + */ +@RequestMapping("/api/v1/customers/{customerId}/") +@RestController +public class BeerOrderController { + + private static final Integer DEFAULT_PAGE_NUMBER = 0; + private static final Integer DEFAULT_PAGE_SIZE = 25; + + private final BeerOrderService beerOrderService; + + public BeerOrderController(BeerOrderService beerOrderService) { + this.beerOrderService = beerOrderService; + } + + @GetMapping("orders") + public BeerOrderPagedList listOrders(@PathVariable("customerId") UUID customerId, + @RequestParam(value = "pageNumber", required = false) Integer pageNumber, + @RequestParam(value = "pageSize", required = false) Integer pageSize){ + + if (pageNumber == null || pageNumber < 0){ + pageNumber = DEFAULT_PAGE_NUMBER; + } + + if (pageSize == null || pageSize < 1) { + pageSize = DEFAULT_PAGE_SIZE; + } + + return beerOrderService.listOrders(customerId, PageRequest.of(pageNumber, pageSize)); + } + + @PostMapping("orders") + @ResponseStatus(HttpStatus.CREATED) + public BeerOrderDto placeOrder(@PathVariable("customerId") UUID customerId, @RequestBody BeerOrderDto beerOrderDto){ + return beerOrderService.placeOrder(customerId, beerOrderDto); + } + + @GetMapping("orders/{orderId}") + public BeerOrderDto getOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId){ + return beerOrderService.getOrderById(customerId, orderId); + } + + @PutMapping("/orders/{orderId}/pickup") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void pickupOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId){ + beerOrderService.pickupOrder(customerId, orderId); + } +} \ No newline at end of file From bee778b23cea45d6b05cd4cca4b3813774891b2f Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:05:47 +0100 Subject: [PATCH 13/30] updated loader for new authority --- .../sfg/brewery/bootstrap/UserLoader.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java index 446a296c1..363c3c9c6 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java @@ -53,14 +53,34 @@ private void loadUserData() { Authority deleteBrewery = authorityRepository.save(Authority.builder().permission("brewery.delete").build()); + //beer order + Authority createOrder = authorityRepository.save(Authority.builder().permission("order.create").build()); + Authority readOrder = authorityRepository.save(Authority.builder().permission("order.read").build()); + Authority updateOrder = authorityRepository.save(Authority.builder().permission("order.update").build()); + Authority deleteOrder = authorityRepository.save(Authority.builder().permission("order.delete").build()); + + Authority createOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.create").build()); + Authority readOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.read").build()); + Authority updateOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.update").build()); + Authority deleteOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.delete").build()); + + Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); Role userRole = roleRepository.save(Role.builder().name("USER").build()); - adminRole.setAuthorities(new HashSet<>(Set.of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, - updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery))); + adminRole.setAuthorities(new HashSet<>(Set + .of(createBeer, updateBeer, readBeer, deleteBeer, + createCustomer, readCustomer, updateCustomer, deleteCustomer, + createBrewery, readBrewery, updateBrewery, deleteBrewery, + createOrder, readOrder, updateOrder, deleteOrder))); - customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery))); + customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, + createOrderCustomer, readOrderCustomer, updateOrderCustomer, deleteOrderCustomer))); userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); From 037370b8858c4f5427d3b70e03b81787f96506f6 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:18:41 +0100 Subject: [PATCH 14/30] updated user to implement userDetail Service --- .../sfg/brewery/domain/security/User.java | 36 ++++++++++++++++--- .../security/JpaUserDetailsService.java | 6 +--- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index a6bd808be..74a02e7c5 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -1,6 +1,10 @@ package guru.sfg.brewery.domain.security; import lombok.*; +import org.springframework.security.core.CredentialsContainer; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import java.util.Set; @@ -15,7 +19,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class User { +public class User implements UserDetails, CredentialsContainer { @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -32,12 +36,11 @@ public class User { private Set roles; @Transient - private Set authorities; - - public Set getAuthorities() { + public Set getAuthorities() { return this.roles.stream() .map(Role::getAuthorities) .flatMap(Set::stream) + .map(auth -> new SimpleGrantedAuthority(auth.getPermission())) .collect(Collectors.toSet()); } @@ -53,4 +56,29 @@ public Set getAuthorities() { @Builder.Default private Boolean enabled = true; + + @Override + public boolean isAccountNonExpired() { + return this.accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return this.accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return this.credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + @Override + public void eraseCredentials() { + this.password = null; + } } \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java index 781240fd1..ff0d429b3 100644 --- a/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java +++ b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java @@ -1,7 +1,6 @@ package guru.sfg.brewery.services.security; import guru.sfg.brewery.domain.security.Authority; -import guru.sfg.brewery.domain.security.User; import guru.sfg.brewery.repositories.security.UserRepository; import lombok.AllArgsConstructor; import org.springframework.security.core.GrantedAuthority; @@ -24,12 +23,9 @@ public class JpaUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = userRepository.findByUsername(username).orElseThrow(() -> { + return userRepository.findByUsername(username).orElseThrow(() -> { return new UsernameNotFoundException("User name: " + username + " not found"); }); - return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), - user.getEnabled(), user.getAccountNonExpired(), user.getCredentialsNonExpired(), - user.getAccountNonLocked(), convertToSpringAuthorities(user.getAuthorities())); } private Collection convertToSpringAuthorities(Set authorities) { From ba9bfd20a934d570cf575bbdf6c1199cb2662428 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:21:46 +0100 Subject: [PATCH 15/30] added mapping between customer and user --- src/main/java/guru/sfg/brewery/domain/Customer.java | 8 +++++--- src/main/java/guru/sfg/brewery/domain/security/User.java | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/domain/Customer.java b/src/main/java/guru/sfg/brewery/domain/Customer.java index bd3c7a40f..b8c314c02 100644 --- a/src/main/java/guru/sfg/brewery/domain/Customer.java +++ b/src/main/java/guru/sfg/brewery/domain/Customer.java @@ -16,14 +16,13 @@ */ package guru.sfg.brewery.domain; +import guru.sfg.brewery.domain.security.User; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.OneToMany; +import javax.persistence.*; import java.sql.Timestamp; import java.util.Set; import java.util.UUID; @@ -54,4 +53,7 @@ public Customer(UUID id, Long version, Timestamp createdDate, Timestamp lastModi @OneToMany(mappedBy = "customer") private Set beerOrders; + @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Set users; + } diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index 74a02e7c5..fac2347fa 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -1,5 +1,6 @@ package guru.sfg.brewery.domain.security; +import guru.sfg.brewery.domain.Customer; import lombok.*; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; @@ -28,6 +29,9 @@ public class User implements UserDetails, CredentialsContainer { private String username; private String password; + @ManyToOne(fetch = FetchType.EAGER) + private Customer customer; + @Singular @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", From 00145e33c824b72068e84b5f0800ffb8ce8a79d5 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:22:15 +0100 Subject: [PATCH 16/30] added mapping between customer and user --- src/main/java/guru/sfg/brewery/config/SecurityConfig.java | 2 -- .../sfg/brewery/web/controllers/api/BeerRestController.java | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index e3f33da51..4416f0f83 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; 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.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -12,7 +11,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration diff --git a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java index be72399db..1d669089e 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java @@ -31,7 +31,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.validation.ConstraintViolationException; From e9b41bc983d61f6f0f6a2fb12d9a376d9b0230d5 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:32:22 +0100 Subject: [PATCH 17/30] updated loader --- .../bootstrap/DefaultBreweryLoader.java | 144 +++++++++++++++++- .../sfg/brewery/domain/security/User.java | 2 +- .../repositories/security/RoleRepository.java | 4 + 3 files changed, 148 insertions(+), 2 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java index c8f04894b..3cc109b01 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java @@ -17,12 +17,22 @@ package guru.sfg.brewery.bootstrap; import guru.sfg.brewery.domain.*; +import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.Role; +import guru.sfg.brewery.domain.security.User; import guru.sfg.brewery.repositories.*; +import guru.sfg.brewery.repositories.security.AuthorityRepository; +import guru.sfg.brewery.repositories.security.RoleRepository; +import guru.sfg.brewery.repositories.security.UserRepository; import guru.sfg.brewery.web.model.BeerStyleEnum; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -30,11 +40,16 @@ /** * Created by jt on 2019-01-26. */ +@Slf4j @RequiredArgsConstructor @Component public class DefaultBreweryLoader implements CommandLineRunner { public static final String TASTING_ROOM = "Tasting Room"; + public static final String ST_PETE_DISTRIBUTING = "St Pete Distributing"; + public static final String DUNEDIN_DISTRIBUTING = "Dunedin Distributing"; + public static final String KEY_WEST_DISTRIBUTORS = "Key West Distributors"; + public static final String BEER_1_UPC = "0631234200036"; public static final String BEER_2_UPC = "0631234300019"; public static final String BEER_3_UPC = "0083783375213"; @@ -44,14 +59,75 @@ public class DefaultBreweryLoader implements CommandLineRunner { private final BeerInventoryRepository beerInventoryRepository; private final BeerOrderRepository beerOrderRepository; private final CustomerRepository customerRepository; + private final AuthorityRepository authorityRepository; + private final RoleRepository roleRepository; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; @Override public void run(String... args) { + loadSecurityData(); loadBreweryData(); + loadTastingRoomData(); loadCustomerData(); } private void loadCustomerData() { + Role customerRole = roleRepository.findByName("CUSTOMER").orElseThrow(); + + //create customers + Customer stPeteCustomer = customerRepository.save(Customer.builder() + .customerName(ST_PETE_DISTRIBUTING) + .apiKey(UUID.randomUUID()) + .build()); + + Customer dunedinCustomer = customerRepository.save(Customer.builder() + .customerName(DUNEDIN_DISTRIBUTING) + .apiKey(UUID.randomUUID()) + .build()); + + Customer keyWestCustomer = customerRepository.save(Customer.builder() + .customerName(KEY_WEST_DISTRIBUTORS) + .apiKey(UUID.randomUUID()) + .build()); + + //create users + User stPeteUser = userRepository.save(User.builder().username("stpete") + .password(passwordEncoder.encode("password")) + .customer(stPeteCustomer) + .role(customerRole).build()); + + User dunedinUser = userRepository.save(User.builder().username("dunedin") + .password(passwordEncoder.encode("password")) + .customer(dunedinCustomer) + .role(customerRole).build()); + + User keywest = userRepository.save(User.builder().username("keywest") + .password(passwordEncoder.encode("password")) + .customer(keyWestCustomer) + .role(customerRole).build()); + + //create orders + createOrder(stPeteCustomer); + createOrder(dunedinCustomer); + createOrder(keyWestCustomer); + + log.debug("Orders Loaded: " + beerOrderRepository.count()); + } + + private BeerOrder createOrder(Customer customer) { + return beerOrderRepository.save(BeerOrder.builder() + .customer(customer) + .orderStatus(OrderStatusEnum.NEW) + .beerOrderLines(Set.of(BeerOrderLine.builder() + .beer(beerRepository.findByUpc(BEER_1_UPC)) + .orderQuantity(2) + .build())) + .build()); + } + + + private void loadTastingRoomData() { Customer tastingRoom = Customer.builder() .customerName(TASTING_ROOM) .apiKey(UUID.randomUUID()) @@ -122,4 +198,70 @@ private void loadBreweryData() { } } -} + + private void loadSecurityData() { + //beer auths + Authority createBeer = authorityRepository.save(Authority.builder().permission("beer.create").build()); + Authority readBeer = authorityRepository.save(Authority.builder().permission("beer.read").build()); + Authority updateBeer = authorityRepository.save(Authority.builder().permission("beer.update").build()); + Authority deleteBeer = authorityRepository.save(Authority.builder().permission("beer.delete").build()); + + //customer auths + Authority createCustomer = authorityRepository.save(Authority.builder().permission("customer.create").build()); + Authority readCustomer = authorityRepository.save(Authority.builder().permission("customer.read").build()); + Authority updateCustomer = authorityRepository.save(Authority.builder().permission("customer.update").build()); + Authority deleteCustomer = authorityRepository.save(Authority.builder().permission("customer.delete").build()); + + //customer brewery + Authority createBrewery = authorityRepository.save(Authority.builder().permission("brewery.create").build()); + Authority readBrewery = authorityRepository.save(Authority.builder().permission("brewery.read").build()); + Authority updateBrewery = authorityRepository.save(Authority.builder().permission("brewery.update").build()); + Authority deleteBrewery = authorityRepository.save(Authority.builder().permission("brewery.delete").build()); + + //beer order + Authority createOrder = authorityRepository.save(Authority.builder().permission("order.create").build()); + Authority readOrder = authorityRepository.save(Authority.builder().permission("order.read").build()); + Authority updateOrder = authorityRepository.save(Authority.builder().permission("order.update").build()); + Authority deleteOrder = authorityRepository.save(Authority.builder().permission("order.delete").build()); + Authority createOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.create").build()); + Authority readOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.read").build()); + Authority updateOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.update").build()); + Authority deleteOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.delete").build()); + + Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); + Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); + Role userRole = roleRepository.save(Role.builder().name("USER").build()); + + adminRole.setAuthorities(new HashSet<>(Set.of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, + updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery, + createOrder, readOrder, updateOrder, deleteOrder))); + + customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer, + updateOrderCustomer, deleteOrderCustomer))); + + userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); + + roleRepository.saveAll(Arrays.asList(adminRole, customerRole, userRole)); + + userRepository.save(User.builder() + .username("spring") + .password(passwordEncoder.encode("guru")) + .role(adminRole) + .build()); + + userRepository.save(User.builder() + .username("user") + .password(passwordEncoder.encode("password")) + .role(userRole) + .build()); + + userRepository.save(User.builder() + .username("scott") + .password(passwordEncoder.encode("tiger")) + .role(customerRole) + .build()); + + log.debug("Users Loaded: " + userRepository.count()); + } + +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index fac2347fa..a159652c5 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -33,7 +33,7 @@ public class User implements UserDetails, CredentialsContainer { private Customer customer; @Singular - @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER) + @ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) diff --git a/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java index 2d112aa40..02616bb65 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java @@ -3,5 +3,9 @@ import guru.sfg.brewery.domain.security.Role; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface RoleRepository extends JpaRepository { + + Optional findByName(String customer); } From fbadef7df142ebaed6973a751fbe617b46a10562 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:32:48 +0100 Subject: [PATCH 18/30] deleted UserLoader --- .../sfg/brewery/bootstrap/UserLoader.java | 110 ------------------ 1 file changed, 110 deletions(-) delete mode 100644 src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java diff --git a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java deleted file mode 100644 index 363c3c9c6..000000000 --- a/src/main/java/guru/sfg/brewery/bootstrap/UserLoader.java +++ /dev/null @@ -1,110 +0,0 @@ -package guru.sfg.brewery.bootstrap; - -import guru.sfg.brewery.domain.security.Authority; -import guru.sfg.brewery.domain.security.Role; -import guru.sfg.brewery.domain.security.User; -import guru.sfg.brewery.repositories.security.AuthorityRepository; -import guru.sfg.brewery.repositories.security.RoleRepository; -import guru.sfg.brewery.repositories.security.UserRepository; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.CommandLineRunner; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -import javax.transaction.Transactional; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -@Slf4j -@AllArgsConstructor -@Component -public class UserLoader implements CommandLineRunner { - - private final PasswordEncoder passwordEncoder; - private final UserRepository userRepository; - private final AuthorityRepository authorityRepository; - private final RoleRepository roleRepository; - - @Override - @Transactional - public void run(String... args) { - loadUserData(); - } - - private void loadUserData() { - //beer auths - Authority createBeer = authorityRepository.save(Authority.builder().permission("beer.create").build()); - Authority readBeer = authorityRepository.save(Authority.builder().permission("beer.read").build()); - Authority updateBeer = authorityRepository.save(Authority.builder().permission("beer.update").build()); - Authority deleteBeer = authorityRepository.save(Authority.builder().permission("beer.delete").build()); - - //customer auths - Authority createCustomer = authorityRepository.save(Authority.builder().permission("customer.create").build()); - Authority readCustomer = authorityRepository.save(Authority.builder().permission("customer.read").build()); - Authority updateCustomer = authorityRepository.save(Authority.builder().permission("customer.update").build()); - Authority deleteCustomer = authorityRepository.save(Authority.builder().permission("customer.delete").build()); - - //customer brewery - Authority createBrewery = authorityRepository.save(Authority.builder().permission("brewery.create").build()); - Authority readBrewery = authorityRepository.save(Authority.builder().permission("brewery.read").build()); - Authority updateBrewery = authorityRepository.save(Authority.builder().permission("brewery.update").build()); - Authority deleteBrewery = authorityRepository.save(Authority.builder().permission("brewery.delete").build()); - - - //beer order - Authority createOrder = authorityRepository.save(Authority.builder().permission("order.create").build()); - Authority readOrder = authorityRepository.save(Authority.builder().permission("order.read").build()); - Authority updateOrder = authorityRepository.save(Authority.builder().permission("order.update").build()); - Authority deleteOrder = authorityRepository.save(Authority.builder().permission("order.delete").build()); - - Authority createOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.create").build()); - Authority readOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.read").build()); - Authority updateOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.update").build()); - Authority deleteOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.delete").build()); - - - Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); - Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); - Role userRole = roleRepository.save(Role.builder().name("USER").build()); - - adminRole.setAuthorities(new HashSet<>(Set - .of(createBeer, updateBeer, readBeer, deleteBeer, - createCustomer, readCustomer, updateCustomer, deleteCustomer, - createBrewery, readBrewery, updateBrewery, deleteBrewery, - createOrder, readOrder, updateOrder, deleteOrder))); - - customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, - createOrderCustomer, readOrderCustomer, updateOrderCustomer, deleteOrderCustomer))); - - userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); - - roleRepository.saveAll(Arrays.asList(adminRole, customerRole, userRole)); - - userRepository.save(User.builder() - .username("spring") - .password(passwordEncoder.encode("guru")) - .role(adminRole) - .build()); - - userRepository.save(User.builder() - .username("user") - .password(passwordEncoder.encode("password")) - .role(userRole) - .build()); - - userRepository.save(User.builder() - .username("scott") - .password(passwordEncoder.encode("password")) - .role(customerRole) - .build()); - - log.debug("Users Loaded: " + userRepository.count()); - - } -} From 8a0fee6185dc1400f5eb4108fd2754479a921e70 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Mon, 11 Jan 2021 21:56:45 +0100 Subject: [PATCH 19/30] added tests --- .../bootstrap/DefaultBreweryLoader.java | 51 +++-- .../repositories/CustomerRepository.java | 4 + .../api/BeerOrderControllerTest.java | 187 ++++++++++++++++++ 3 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java diff --git a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java index 3cc109b01..0138a2d7e 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java @@ -1,19 +1,3 @@ -/* - * Copyright 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ package guru.sfg.brewery.bootstrap; import guru.sfg.brewery.domain.*; @@ -49,6 +33,9 @@ public class DefaultBreweryLoader implements CommandLineRunner { public static final String ST_PETE_DISTRIBUTING = "St Pete Distributing"; public static final String DUNEDIN_DISTRIBUTING = "Dunedin Distributing"; public static final String KEY_WEST_DISTRIBUTORS = "Key West Distributors"; + public static final String STPETE_USER = "stpete"; + public static final String DUNEDIN_USER = "dunedin"; + public static final String KEYWEST_USER = "keywest"; public static final String BEER_1_UPC = "0631234200036"; public static final String BEER_2_UPC = "0631234300019"; @@ -92,17 +79,17 @@ private void loadCustomerData() { .build()); //create users - User stPeteUser = userRepository.save(User.builder().username("stpete") + User stPeteUser = userRepository.save(User.builder().username(STPETE_USER) .password(passwordEncoder.encode("password")) .customer(stPeteCustomer) .role(customerRole).build()); - User dunedinUser = userRepository.save(User.builder().username("dunedin") + User dunedinUser = userRepository.save(User.builder().username(DUNEDIN_USER) .password(passwordEncoder.encode("password")) .customer(dunedinCustomer) .role(customerRole).build()); - User keywest = userRepository.save(User.builder().username("keywest") + User keywest = userRepository.save(User.builder().username(KEYWEST_USER) .password(passwordEncoder.encode("password")) .customer(keyWestCustomer) .role(customerRole).build()); @@ -116,7 +103,7 @@ private void loadCustomerData() { } private BeerOrder createOrder(Customer customer) { - return beerOrderRepository.save(BeerOrder.builder() + return beerOrderRepository.save(BeerOrder.builder() .customer(customer) .orderStatus(OrderStatusEnum.NEW) .beerOrderLines(Set.of(BeerOrderLine.builder() @@ -223,21 +210,27 @@ private void loadSecurityData() { Authority readOrder = authorityRepository.save(Authority.builder().permission("order.read").build()); Authority updateOrder = authorityRepository.save(Authority.builder().permission("order.update").build()); Authority deleteOrder = authorityRepository.save(Authority.builder().permission("order.delete").build()); - Authority createOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.create").build()); - Authority readOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.read").build()); - Authority updateOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.update").build()); - Authority deleteOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.delete").build()); + Authority createOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.create").build()); + Authority readOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.read").build()); + Authority updateOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.update").build()); + Authority deleteOrderCustomer = authorityRepository + .save(Authority.builder().permission("customer.order.delete").build()); Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); Role userRole = roleRepository.save(Role.builder().name("USER").build()); - adminRole.setAuthorities(new HashSet<>(Set.of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, - updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery, - createOrder, readOrder, updateOrder, deleteOrder))); + adminRole.setAuthorities(new HashSet<>(Set + .of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, + updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery, + createOrder, readOrder, updateOrder, deleteOrder))); - customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer, - updateOrderCustomer, deleteOrderCustomer))); + customerRole.setAuthorities(new HashSet<>(Set + .of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer, + updateOrderCustomer, deleteOrderCustomer))); userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); diff --git a/src/main/java/guru/sfg/brewery/repositories/CustomerRepository.java b/src/main/java/guru/sfg/brewery/repositories/CustomerRepository.java index 5a634ca38..abe065ce7 100644 --- a/src/main/java/guru/sfg/brewery/repositories/CustomerRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/CustomerRepository.java @@ -17,9 +17,11 @@ package guru.sfg.brewery.repositories; import guru.sfg.brewery.domain.Customer; +import guru.sfg.brewery.domain.security.Role; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; import java.util.UUID; /** @@ -27,4 +29,6 @@ */ public interface CustomerRepository extends JpaRepository { List findAllByCustomerNameLike(String customerName); + + Optional findAllByCustomerName(String customerName); } diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java new file mode 100644 index 000000000..82d9f1647 --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java @@ -0,0 +1,187 @@ +package guru.sfg.brewery.web.controllers.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.sfg.brewery.bootstrap.DefaultBreweryLoader; +import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.domain.Customer; +import guru.sfg.brewery.repositories.BeerOrderRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.web.controllers.BaseIT; +import guru.sfg.brewery.web.model.BeerOrderDto; +import guru.sfg.brewery.web.model.BeerOrderLineDto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +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.status; + +@SpringBootTest +class BeerOrderControllerTest extends BaseIT { + + public static final String API_ROOT = "/api/v1/customers/"; + + @Autowired + CustomerRepository customerRepository; + + @Autowired + BeerOrderRepository beerOrderRepository; + + @Autowired + BeerRepository beerRepository; + + @Autowired + ObjectMapper objectMapper; + + Customer stPeteCustomer; + Customer dunedinCustomer; + Customer keyWestCustomer; + List loadedBeers; + + @BeforeEach + void setUp() { + stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING) + .orElseThrow(); + dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING) + .orElseThrow(); + keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS) + .orElseThrow(); + loadedBeers = beerRepository.findAll(); + } + +//cant use nested tests bug - https://github.com/spring-projects/spring-security/issues/8793 +// @DisplayName("Create Test") +// @Nested +// class createOrderTests { + + + @Test + void createOrderNotAuth() throws Exception { + BeerOrderDto beerOrderDto = buildOrderDto(stPeteCustomer, loadedBeers.get(0).getId()); + + mockMvc.perform(post(API_ROOT + stPeteCustomer.getId() + "/orders") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beerOrderDto))) + .andExpect(status().isUnauthorized()); + } + + @WithUserDetails("spring") + @Test + void createOrderUserAdmin() throws Exception { + BeerOrderDto beerOrderDto = buildOrderDto(stPeteCustomer, loadedBeers.get(0).getId()); + + mockMvc.perform(post(API_ROOT + stPeteCustomer.getId() + "/orders") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beerOrderDto))) + .andExpect(status().isCreated()); + } + + @WithUserDetails(DefaultBreweryLoader.STPETE_USER) + @Test + void createOrderUserAuthCustomer() throws Exception { + BeerOrderDto beerOrderDto = buildOrderDto(stPeteCustomer, loadedBeers.get(0).getId()); + + mockMvc.perform(post(API_ROOT + stPeteCustomer.getId() + "/orders") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beerOrderDto))) + .andExpect(status().isCreated()); + } + + @WithUserDetails(DefaultBreweryLoader.KEYWEST_USER) + @Test + void createOrderUserNOTAuthCustomer() throws Exception { + BeerOrderDto beerOrderDto = buildOrderDto(stPeteCustomer, loadedBeers.get(0).getId()); + + mockMvc.perform(post(API_ROOT + stPeteCustomer.getId() + "/orders") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beerOrderDto))) + .andExpect(status().isForbidden()); + } + + // } + @Test + void listOrdersNotAuth() throws Exception { + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders")) + .andExpect(status().isUnauthorized()); + } + + @WithUserDetails(value = "spring") + @Test + void listOrdersAdminAuth() throws Exception { + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders")) + .andExpect(status().isOk()); + } + + @WithUserDetails(value = DefaultBreweryLoader.STPETE_USER) + @Test + void listOrdersCustomerAuth() throws Exception { + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders")) + .andExpect(status().isOk()); + } + + @WithUserDetails(value = DefaultBreweryLoader.DUNEDIN_USER) + @Test + void listOrdersCustomerNOTAuth() throws Exception { + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders")) + .andExpect(status().isForbidden()); + } + + @Test + void listOrdersNoAuth() throws Exception { + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId())) + .andExpect(status().isUnauthorized()); + } + + @Disabled + @Test + void pickUpOrderNotAuth() { + } + + @Disabled + @Test + void pickUpOrderNotAdminUser() { + } + + @Disabled + @Test + void pickUpOrderCustomerUserAUTH() { + } + + @Disabled + @Test + void pickUpOrderCustomerUserNOT_AUTH() { + } + + private BeerOrderDto buildOrderDto(Customer customer, UUID beerId) { + List orderLines = Arrays.asList(BeerOrderLineDto.builder() + .id(UUID.randomUUID()) + .beerId(beerId) + .orderQuantity(5) + .build()); + + return BeerOrderDto.builder() + .customerId(customer.getId()) + .customerRef("123") + .orderStatusCallbackUrl("http://example.com") + .beerOrderLines(orderLines) + .build(); + } +} \ No newline at end of file From aee1e4eaf17247f595f9d16b2fcf88e00ea0ff55 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Tue, 12 Jan 2021 22:40:37 +0100 Subject: [PATCH 20/30] spring security with Spring data jpa --- pom.xml | 4 + .../bootstrap/DefaultBreweryLoader.java | 46 ++++--- .../config/BeerOrderAuthenticationManger.java | 19 +++ .../sfg/brewery/config/SecurityConfig.java | 8 +- .../permission/BeerCreatePermission.java | 3 + .../permission/BeerDeletePermission.java | 3 + .../permission/BeerOrderCreatePermission.java | 16 +++ .../permission/BeerOrderPickupPermission.java | 16 +++ .../permission/BeerOrderReadPermission.java | 16 +++ .../permission/BeerOrderReadPermissionV2.java | 14 ++ .../permission/BeerReadPermission.java | 3 + .../permission/BeerUpdatePermission.java | 3 + .../permission/BreweryCreatePermission.java | 12 ++ .../permission/BreweryDeletePermission.java | 11 ++ .../permission/BreweryReadPermission.java | 11 ++ .../permission/BreweryUpdatePermission.java | 12 ++ .../permission/CustomerCreatePermission.java | 12 ++ .../permission/CustomerDeletePermission.java | 11 ++ .../permission/CustomerReadPermission.java | 10 ++ .../permission/CustomerUpdatePermission.java | 11 ++ .../repositories/BeerOrderRepository.java | 5 + .../brewery/services/BeerOrderService.java | 4 + .../services/BeerOrderServiceImpl.java | 39 +++--- .../web/controllers/BreweryController.java | 7 +- .../web/controllers/CustomerController.java | 24 ++-- .../controllers/api/BeerOrderController.java | 36 ++++- .../api/BeerOrderControllerV2.java | 63 +++++++++ .../api/BeerOrderControllerTest.java | 51 +++++++- .../api/BeerOrderControllerV2Test.java | 123 ++++++++++++++++++ 29 files changed, 534 insertions(+), 59 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/config/BeerOrderAuthenticationManger.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerOrderCreatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerOrderPickupPermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermissionV2.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BreweryCreatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BreweryDeletePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BreweryReadPermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/BreweryUpdatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/CustomerCreatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/CustomerDeletePermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/CustomerReadPermission.java create mode 100644 src/main/java/guru/sfg/brewery/permission/CustomerUpdatePermission.java create mode 100644 src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2.java create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java diff --git a/pom.xml b/pom.xml index 4be087fcc..325766c77 100644 --- a/pom.xml +++ b/pom.xml @@ -147,6 +147,10 @@ spring-security-test test + + org.springframework.security + spring-security-data + diff --git a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java index 0138a2d7e..6bda19322 100644 --- a/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java +++ b/src/main/java/guru/sfg/brewery/bootstrap/DefaultBreweryLoader.java @@ -1,3 +1,19 @@ +/* + * Copyright 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package guru.sfg.brewery.bootstrap; import guru.sfg.brewery.domain.*; @@ -103,7 +119,7 @@ private void loadCustomerData() { } private BeerOrder createOrder(Customer customer) { - return beerOrderRepository.save(BeerOrder.builder() + return beerOrderRepository.save(BeerOrder.builder() .customer(customer) .orderStatus(OrderStatusEnum.NEW) .beerOrderLines(Set.of(BeerOrderLine.builder() @@ -210,27 +226,23 @@ private void loadSecurityData() { Authority readOrder = authorityRepository.save(Authority.builder().permission("order.read").build()); Authority updateOrder = authorityRepository.save(Authority.builder().permission("order.update").build()); Authority deleteOrder = authorityRepository.save(Authority.builder().permission("order.delete").build()); - Authority createOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.create").build()); - Authority readOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.read").build()); - Authority updateOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.update").build()); - Authority deleteOrderCustomer = authorityRepository - .save(Authority.builder().permission("customer.order.delete").build()); + Authority pickupOrder = authorityRepository.save(Authority.builder().permission("order.pickup").build()); + Authority createOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.create").build()); + Authority readOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.read").build()); + Authority updateOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.update").build()); + Authority deleteOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.delete").build()); + Authority pickupOrderCustomer = authorityRepository.save(Authority.builder().permission("customer.order.pickup").build()); Role adminRole = roleRepository.save(Role.builder().name("ADMIN").build()); Role customerRole = roleRepository.save(Role.builder().name("CUSTOMER").build()); Role userRole = roleRepository.save(Role.builder().name("USER").build()); - adminRole.setAuthorities(new HashSet<>(Set - .of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, - updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery, - createOrder, readOrder, updateOrder, deleteOrder))); + adminRole.setAuthorities(new HashSet<>(Set.of(createBeer, updateBeer, readBeer, deleteBeer, createCustomer, readCustomer, + updateCustomer, deleteCustomer, createBrewery, readBrewery, updateBrewery, deleteBrewery, + createOrder, readOrder, updateOrder, deleteOrder, pickupOrder))); - customerRole.setAuthorities(new HashSet<>(Set - .of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer, - updateOrderCustomer, deleteOrderCustomer))); + customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer, + updateOrderCustomer, deleteOrderCustomer, pickupOrderCustomer))); userRole.setAuthorities(new HashSet<>(Set.of(readBeer))); @@ -257,4 +269,4 @@ private void loadSecurityData() { log.debug("Users Loaded: " + userRepository.count()); } -} \ No newline at end of file +} diff --git a/src/main/java/guru/sfg/brewery/config/BeerOrderAuthenticationManger.java b/src/main/java/guru/sfg/brewery/config/BeerOrderAuthenticationManger.java new file mode 100644 index 000000000..3315e8d2e --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/BeerOrderAuthenticationManger.java @@ -0,0 +1,19 @@ +package guru.sfg.brewery.config; + +import guru.sfg.brewery.domain.security.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Slf4j +@Component +public class BeerOrderAuthenticationManger { + + public boolean customerIdMatches(Authentication authentication, UUID customerId) { + User authenticatedUser = (User) authentication.getPrincipal(); + log.debug("Auth User Customer ID" + authenticatedUser.getCustomer().getId() + " Customer Id: " + customerId); + return authenticatedUser.getCustomer().getId().equals(customerId); + } +} diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 4416f0f83..4fa73f508 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -11,6 +11,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @@ -21,6 +22,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JpaUserDetailsService jpaUserDetailsService; + @Bean + public SecurityEvaluationContextExtension securityEvaluationContextExtension() { + return new SecurityEvaluationContextExtension(); + } + public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager) { RestHeaderAuthFilter filter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**")); filter.setAuthenticationManager(authenticationManager); @@ -35,7 +41,7 @@ protected void configure(HttpSecurity http) throws Exception { authorize .antMatchers("/h2-console/**").permitAll() //do not use in production! .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); - } ) + }) .authorizeRequests() .anyRequest().authenticated() .and() diff --git a/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java index 69ad9037c..4750ac4de 100644 --- a/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java +++ b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * Created by jt on 6/30/20. + */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAuthority('beer.create')") public @interface BeerCreatePermission { diff --git a/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java index 9882a7b09..a8f7d9959 100644 --- a/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java +++ b/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * Created by jt on 6/30/20. + */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAuthority('beer.delete')") public @interface BeerDeletePermission { diff --git a/src/main/java/guru/sfg/brewery/permission/BeerOrderCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerOrderCreatePermission.java new file mode 100644 index 000000000..077edb5d0 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerOrderCreatePermission.java @@ -0,0 +1,16 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by jt on 7/7/20. + */ +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('order.create') OR " + + "hasAuthority('customer.order.create') " + + " AND @beerOrderAuthenticationManger.customerIdMatches(authentication, #customerId )") +public @interface BeerOrderCreatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerOrderPickupPermission.java b/src/main/java/guru/sfg/brewery/permission/BeerOrderPickupPermission.java new file mode 100644 index 000000000..58bb62065 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerOrderPickupPermission.java @@ -0,0 +1,16 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by jt on 7/7/20. + */ +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('order.pickup') OR " + + "hasAuthority('customer.order.pickup') " + + " AND @beerOrderAuthenticationManger.customerIdMatches(authentication, #customerId )") +public @interface BeerOrderPickupPermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermission.java b/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermission.java new file mode 100644 index 000000000..79038eb7e --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermission.java @@ -0,0 +1,16 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by jt on 7/7/20. + */ +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('order.read') OR " + + "hasAuthority('customer.order.read') " + + " AND @beerOrderAuthenticationManger.customerIdMatches(authentication, #customerId )") +public @interface BeerOrderReadPermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermissionV2.java b/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermissionV2.java new file mode 100644 index 000000000..fee96fc34 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BeerOrderReadPermissionV2.java @@ -0,0 +1,14 @@ +package guru.sfg.brewery.permission; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by jt on 7/7/20. + */ + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAnyAuthority('order.read', 'customer.order.read')") +public @interface BeerOrderReadPermissionV2 { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java b/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java index 07d8ff0d0..ece56cc43 100644 --- a/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java +++ b/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * Created by jt on 6/30/20. + */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAuthority('beer.read')") public @interface BeerReadPermission { diff --git a/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java index 7802216f4..cf2dc28a4 100644 --- a/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java +++ b/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +/** + * Created by jt on 6/30/20. + */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAuthority('beer.update')") public @interface BeerUpdatePermission { diff --git a/src/main/java/guru/sfg/brewery/permission/BreweryCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/BreweryCreatePermission.java new file mode 100644 index 000000000..d9e744a4b --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BreweryCreatePermission.java @@ -0,0 +1,12 @@ +package guru.sfg.brewery.permission; + + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('brewery.create')") +public @interface BreweryCreatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BreweryDeletePermission.java b/src/main/java/guru/sfg/brewery/permission/BreweryDeletePermission.java new file mode 100644 index 000000000..126a4f45a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BreweryDeletePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('brewery.delete')") +public @interface BreweryDeletePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BreweryReadPermission.java b/src/main/java/guru/sfg/brewery/permission/BreweryReadPermission.java new file mode 100644 index 000000000..58754e084 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BreweryReadPermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('brewery.read')") +public @interface BreweryReadPermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/BreweryUpdatePermission.java b/src/main/java/guru/sfg/brewery/permission/BreweryUpdatePermission.java new file mode 100644 index 000000000..9b41b1836 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/BreweryUpdatePermission.java @@ -0,0 +1,12 @@ +package guru.sfg.brewery.permission; + + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('brewery.update')") +public @interface BreweryUpdatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/CustomerCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/CustomerCreatePermission.java new file mode 100644 index 000000000..5ce2a6c74 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/CustomerCreatePermission.java @@ -0,0 +1,12 @@ +package guru.sfg.brewery.permission; + + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('customer.create')") +public @interface CustomerCreatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/CustomerDeletePermission.java b/src/main/java/guru/sfg/brewery/permission/CustomerDeletePermission.java new file mode 100644 index 000000000..6b7aab9b3 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/CustomerDeletePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('customer.delete')") +public @interface CustomerDeletePermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/CustomerReadPermission.java b/src/main/java/guru/sfg/brewery/permission/CustomerReadPermission.java new file mode 100644 index 000000000..35cbcec4a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/CustomerReadPermission.java @@ -0,0 +1,10 @@ +package guru.sfg.brewery.permission; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('customer.read')") +public @interface CustomerReadPermission { +} diff --git a/src/main/java/guru/sfg/brewery/permission/CustomerUpdatePermission.java b/src/main/java/guru/sfg/brewery/permission/CustomerUpdatePermission.java new file mode 100644 index 000000000..fa4572b4a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/permission/CustomerUpdatePermission.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.permission; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAuthority('customer.update')") +public @interface CustomerUpdatePermission { +} diff --git a/src/main/java/guru/sfg/brewery/repositories/BeerOrderRepository.java b/src/main/java/guru/sfg/brewery/repositories/BeerOrderRepository.java index 80fd28c07..671a82e16 100644 --- a/src/main/java/guru/sfg/brewery/repositories/BeerOrderRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/BeerOrderRepository.java @@ -23,6 +23,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; import javax.persistence.LockModeType; import java.util.List; @@ -39,4 +40,8 @@ public interface BeerOrderRepository extends JpaRepository { @Lock(LockModeType.PESSIMISTIC_WRITE) BeerOrder findOneById(UUID id); + + @Query("select o from BeerOrder o where o.id =?1 and " + + "(true = :#{hasAuthority('order.read')} or o.customer.id = ?#{principal?.customer?.id})") + BeerOrder findOrderByIdSecure(UUID orderId); } diff --git a/src/main/java/guru/sfg/brewery/services/BeerOrderService.java b/src/main/java/guru/sfg/brewery/services/BeerOrderService.java index 082b132be..ae5ec9b9d 100644 --- a/src/main/java/guru/sfg/brewery/services/BeerOrderService.java +++ b/src/main/java/guru/sfg/brewery/services/BeerOrderService.java @@ -32,4 +32,8 @@ public interface BeerOrderService { BeerOrderDto getOrderById(UUID customerId, UUID orderId); void pickupOrder(UUID customerId, UUID orderId); + + BeerOrderPagedList listOrders(Pageable pageable); + + BeerOrderDto getOrderById(UUID orderId); } diff --git a/src/main/java/guru/sfg/brewery/services/BeerOrderServiceImpl.java b/src/main/java/guru/sfg/brewery/services/BeerOrderServiceImpl.java index 559250a0f..25b9a19e9 100644 --- a/src/main/java/guru/sfg/brewery/services/BeerOrderServiceImpl.java +++ b/src/main/java/guru/sfg/brewery/services/BeerOrderServiceImpl.java @@ -1,20 +1,3 @@ -/* - * Copyright 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package guru.sfg.brewery.services; import guru.sfg.brewery.domain.BeerOrder; @@ -66,6 +49,19 @@ public BeerOrderPagedList listOrders(UUID customerId, Pageable pageable) { } } + @Override + public BeerOrderPagedList listOrders(Pageable pageable) { + Page beerOrderPage = beerOrderRepository.findAll(pageable); + + return new BeerOrderPagedList(beerOrderPage + .stream() + .map(beerOrderMapper::beerOrderToDto) + .collect(Collectors.toList()), PageRequest.of( + beerOrderPage.getPageable().getPageNumber(), + beerOrderPage.getPageable().getPageSize()), + beerOrderPage.getTotalElements()); + } + @Transactional @Override public BeerOrderDto placeOrder(UUID customerId, BeerOrderDto beerOrderDto) { @@ -94,6 +90,13 @@ public BeerOrderDto getOrderById(UUID customerId, UUID orderId) { return beerOrderMapper.beerOrderToDto(getOrder(customerId, orderId)); } + @Override + public BeerOrderDto getOrderById(UUID orderId) { + BeerOrder beerOrder = beerOrderRepository.findOrderByIdSecure(orderId); + + return beerOrderMapper.beerOrderToDto(beerOrder); + } + @Override public void pickupOrder(UUID customerId, UUID orderId) { BeerOrder beerOrder = getOrder(customerId, orderId); @@ -120,4 +123,4 @@ private BeerOrder getOrder(UUID customerId, UUID orderId){ } throw new RuntimeException("Customer Not Found"); } -} +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/web/controllers/BreweryController.java b/src/main/java/guru/sfg/brewery/web/controllers/BreweryController.java index 82d2f9226..9dbdc1adf 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/BreweryController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/BreweryController.java @@ -18,6 +18,7 @@ package guru.sfg.brewery.web.controllers; import guru.sfg.brewery.domain.Brewery; +import guru.sfg.brewery.permission.BreweryReadPermission; import guru.sfg.brewery.services.BreweryService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; @@ -36,14 +37,16 @@ public class BreweryController { private final BreweryService breweryService; + @BreweryReadPermission @GetMapping({"/breweries", "/breweries/index", "/breweries/index.html", "/breweries.html"}) public String listBreweries(Model model) { model.addAttribute("breweries", breweryService.getAllBreweries()); return "breweries/index"; } + + @BreweryReadPermission @GetMapping("/api/v1/breweries") - public @ResponseBody - List getBreweriesJson(){ + public @ResponseBody List getBreweriesJson(){ return breweryService.getAllBreweries(); } } diff --git a/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java b/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java index 042426380..64bb09799 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/CustomerController.java @@ -18,10 +18,11 @@ package guru.sfg.brewery.web.controllers; import guru.sfg.brewery.domain.Customer; +import guru.sfg.brewery.permission.CustomerCreatePermission; +import guru.sfg.brewery.permission.CustomerReadPermission; +import guru.sfg.brewery.permission.CustomerUpdatePermission; import guru.sfg.brewery.repositories.CustomerRepository; import lombok.RequiredArgsConstructor; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -43,15 +44,16 @@ public class CustomerController { //ToDO: Add service private final CustomerRepository customerRepository; + @CustomerReadPermission @RequestMapping("/find") - public String findCustomers(Model model){ + public String findCustomers(Model model) { model.addAttribute("customer", Customer.builder().build()); return "customers/findCustomers"; } - @Secured({"ROLE_ADMIN", "ROLE_CUSTOMER"}) + @CustomerReadPermission @GetMapping - public String processFindFormReturnMany(Customer customer, BindingResult result, Model model){ + public String processFindFormReturnMany(Customer customer, BindingResult result, Model model) { // find customers by name //ToDO: Add Service List customers = customerRepository.findAllByCustomerNameLike("%" + customer.getCustomerName() + "%"); @@ -70,6 +72,7 @@ public String processFindFormReturnMany(Customer customer, BindingResult result, } } + @CustomerReadPermission @GetMapping("/{customerId}") public ModelAndView showCustomer(@PathVariable UUID customerId) { ModelAndView mav = new ModelAndView("customers/customerDetails"); @@ -78,13 +81,14 @@ public ModelAndView showCustomer(@PathVariable UUID customerId) { return mav; } + @CustomerCreatePermission @GetMapping("/new") public String initCreationForm(Model model) { model.addAttribute("customer", Customer.builder().build()); return "customers/createCustomer"; } - @PreAuthorize("hasRole('ADMIN')") + @CustomerCreatePermission @PostMapping("/new") public String processCreationForm(Customer customer) { //ToDO: Add Service @@ -92,24 +96,26 @@ public String processCreationForm(Customer customer) { .customerName(customer.getCustomerName()) .build(); - Customer savedCustomer= customerRepository.save(newCustomer); + Customer savedCustomer = customerRepository.save(newCustomer); return "redirect:/customers/" + savedCustomer.getId(); } + @CustomerUpdatePermission @GetMapping("/{customerId}/edit") public String initUpdateCustomerForm(@PathVariable UUID customerId, Model model) { - if(customerRepository.findById(customerId).isPresent()) + if (customerRepository.findById(customerId).isPresent()) model.addAttribute("customer", customerRepository.findById(customerId).get()); return "customers/createOrUpdateCustomer"; } + @CustomerUpdatePermission @PostMapping("/{beerId}/edit") public String processUpdationForm(@Valid Customer customer, BindingResult result) { if (result.hasErrors()) { return "beers/createOrUpdateCustomer"; } else { //ToDO: Add Service - Customer savedCustomer = customerRepository.save(customer); + Customer savedCustomer = customerRepository.save(customer); return "redirect:/customers/" + savedCustomer.getId(); } } diff --git a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java index 9675bfa2f..1a1c42fcf 100644 --- a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java +++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java @@ -1,5 +1,25 @@ +/* + * Copyright 2019 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package guru.sfg.brewery.web.controllers.api; +import guru.sfg.brewery.permission.BeerOrderCreatePermission; +import guru.sfg.brewery.permission.BeerOrderPickupPermission; +import guru.sfg.brewery.permission.BeerOrderReadPermission; import guru.sfg.brewery.services.BeerOrderService; import guru.sfg.brewery.web.model.BeerOrderDto; import guru.sfg.brewery.web.model.BeerOrderPagedList; @@ -25,12 +45,13 @@ public BeerOrderController(BeerOrderService beerOrderService) { this.beerOrderService = beerOrderService; } + @BeerOrderReadPermission @GetMapping("orders") public BeerOrderPagedList listOrders(@PathVariable("customerId") UUID customerId, @RequestParam(value = "pageNumber", required = false) Integer pageNumber, - @RequestParam(value = "pageSize", required = false) Integer pageSize){ + @RequestParam(value = "pageSize", required = false) Integer pageSize) { - if (pageNumber == null || pageNumber < 0){ + if (pageNumber == null || pageNumber < 0) { pageNumber = DEFAULT_PAGE_NUMBER; } @@ -41,20 +62,23 @@ public BeerOrderPagedList listOrders(@PathVariable("customerId") UUID customerId return beerOrderService.listOrders(customerId, PageRequest.of(pageNumber, pageSize)); } + @BeerOrderCreatePermission @PostMapping("orders") @ResponseStatus(HttpStatus.CREATED) - public BeerOrderDto placeOrder(@PathVariable("customerId") UUID customerId, @RequestBody BeerOrderDto beerOrderDto){ + public BeerOrderDto placeOrder(@PathVariable("customerId") UUID customerId, @RequestBody BeerOrderDto beerOrderDto) { return beerOrderService.placeOrder(customerId, beerOrderDto); } + @BeerOrderReadPermission @GetMapping("orders/{orderId}") - public BeerOrderDto getOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId){ + public BeerOrderDto getOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId) { return beerOrderService.getOrderById(customerId, orderId); } + @BeerOrderPickupPermission @PutMapping("/orders/{orderId}/pickup") @ResponseStatus(HttpStatus.NO_CONTENT) - public void pickupOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId){ + public void pickupOrder(@PathVariable("customerId") UUID customerId, @PathVariable("orderId") UUID orderId) { beerOrderService.pickupOrder(customerId, orderId); } -} \ No newline at end of file +} diff --git a/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2.java new file mode 100644 index 000000000..d36f2e7a5 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2.java @@ -0,0 +1,63 @@ +package guru.sfg.brewery.web.controllers.api; + +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.permission.BeerOrderReadPermissionV2; +import guru.sfg.brewery.services.BeerOrderService; +import guru.sfg.brewery.web.model.BeerOrderDto; +import guru.sfg.brewery.web.model.BeerOrderPagedList; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.UUID; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v2/orders/") +public class BeerOrderControllerV2 { + + private static final Integer DEFAULT_PAGE_NUMBER = 0; + private static final Integer DEFAULT_PAGE_SIZE = 25; + + private final BeerOrderService beerOrderService; + + @BeerOrderReadPermissionV2 + @GetMapping + public BeerOrderPagedList listOrders(@AuthenticationPrincipal User user, + @RequestParam(value = "pageNumber", required = false) Integer pageNumber, + @RequestParam(value = "pageSize", required = false) Integer pageSize){ + + if (pageNumber == null || pageNumber < 0){ + pageNumber = DEFAULT_PAGE_NUMBER; + } + + if (pageSize == null || pageSize < 1) { + pageSize = DEFAULT_PAGE_SIZE; + } + + if (user.getCustomer() != null) { + return beerOrderService.listOrders(user.getCustomer().getId(), PageRequest.of(pageNumber, pageSize)); + } else { + return beerOrderService.listOrders(PageRequest.of(pageNumber, pageSize)); + } + } + + @BeerOrderReadPermissionV2 + @GetMapping("{orderId}") + public BeerOrderDto getOrder(@PathVariable("orderId") UUID orderId){ + BeerOrderDto beerOrderDto = beerOrderService.getOrderById(orderId); + + if (beerOrderDto == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Order Not Found"); + } + + log.debug("Found Order: " + beerOrderDto); + + return beerOrderDto; + } +} diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java index 82d9f1647..2abfb379e 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java @@ -1,8 +1,10 @@ package guru.sfg.brewery.web.controllers.api; + import com.fasterxml.jackson.databind.ObjectMapper; import guru.sfg.brewery.bootstrap.DefaultBreweryLoader; import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.domain.BeerOrder; import guru.sfg.brewery.domain.Customer; import guru.sfg.brewery.repositories.BeerOrderRepository; import guru.sfg.brewery.repositories.BeerRepository; @@ -17,6 +19,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; @@ -50,12 +53,9 @@ class BeerOrderControllerTest extends BaseIT { @BeforeEach void setUp() { - stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING) - .orElseThrow(); - dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING) - .orElseThrow(); - keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS) - .orElseThrow(); + stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING).orElseThrow(); + dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING).orElseThrow(); + keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS).orElseThrow(); loadedBeers = beerRepository.findAll(); } @@ -150,6 +150,45 @@ void listOrdersNoAuth() throws Exception { .andExpect(status().isUnauthorized()); } + @Transactional + @Test + void getByOrderIdNotAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId())) + .andExpect(status().isUnauthorized()); + } + + @Transactional + @WithUserDetails("spring") + @Test + void getByOrderIdADMIN() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId())) + .andExpect(status().is2xxSuccessful()); + } + + @Transactional + @WithUserDetails(DefaultBreweryLoader.STPETE_USER) + @Test + void getByOrderIdCustomerAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId())) + .andExpect(status().is2xxSuccessful()); + } + + @Transactional + @WithUserDetails(DefaultBreweryLoader.DUNEDIN_USER) + @Test + void getByOrderIdCustomerNOTAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId())) + .andExpect(status().isForbidden()); + } + @Disabled @Test void pickUpOrderNotAuth() { diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java new file mode 100644 index 000000000..fe6298ae7 --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java @@ -0,0 +1,123 @@ +package guru.sfg.brewery.web.controllers.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.sfg.brewery.bootstrap.DefaultBreweryLoader; +import guru.sfg.brewery.domain.Beer; +import guru.sfg.brewery.domain.BeerOrder; +import guru.sfg.brewery.domain.Customer; +import guru.sfg.brewery.repositories.BeerOrderRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.web.controllers.BaseIT; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +class BeerOrderControllerV2Test extends BaseIT { + public static final String API_ROOT = "/api/v2/orders/"; + + @Autowired + CustomerRepository customerRepository; + + @Autowired + BeerOrderRepository beerOrderRepository; + + @Autowired + BeerRepository beerRepository; + + @Autowired + ObjectMapper objectMapper; + + Customer stPeteCustomer; + Customer dunedinCustomer; + Customer keyWestCustomer; + List loadedBeers; + + @BeforeEach + void setUp() { + stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING) + .orElseThrow(); + dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING) + .orElseThrow(); + keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS) + .orElseThrow(); + loadedBeers = beerRepository.findAll(); + } + + @Test + void listOrdersNotAuth() throws Exception { + mockMvc.perform(get(API_ROOT)) + .andExpect(status().isUnauthorized()); + } + + @WithUserDetails(value = "spring") + @Test + void listOrdersAdminAuth() throws Exception { + mockMvc.perform(get(API_ROOT)) + .andExpect(status().isOk()); + } + + @WithUserDetails(value = DefaultBreweryLoader.STPETE_USER) + @Test + void listOrdersCustomerAuth() throws Exception { + mockMvc.perform(get(API_ROOT)) + .andExpect(status().isOk()); + } + + @WithUserDetails(value = DefaultBreweryLoader.DUNEDIN_USER) + @Test + void listOrdersCustomerDunedinAuth() throws Exception { + mockMvc.perform(get(API_ROOT)) + .andExpect(status().isOk()); + } + + @Transactional + @Test + void getByOrderIdNotAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + beerOrder.getId())) + .andExpect(status().isUnauthorized()); + } + + + @Transactional + @WithUserDetails("spring") + @Test + void getByOrderIdADMIN() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + beerOrder.getId())) + .andExpect(status().is2xxSuccessful()); + } + + @Transactional + @WithUserDetails(DefaultBreweryLoader.STPETE_USER) + @Test + void getByOrderIdCustomerAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + beerOrder.getId())) + .andExpect(status().is2xxSuccessful()); + } + + @Transactional + @WithUserDetails(DefaultBreweryLoader.DUNEDIN_USER) + @Test + void getByOrderIdCustomerNOTAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(get(API_ROOT + beerOrder.getId())) + .andExpect(status().isForbidden()); + } +} \ No newline at end of file From ab2dbb6714976c20b47800113dbbf471e0bc7cf9 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 17:30:34 +0100 Subject: [PATCH 21/30] added crsf --- .../sfg/brewery/config/SecurityConfig.java | 90 +++++++++---------- .../api/BeerOrderControllerTest.java | 41 ++++++--- .../api/BeerOrderControllerV2Test.java | 16 ++-- .../controllers/api/BeerRestControllerIT.java | 2 +- 4 files changed, 80 insertions(+), 69 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 4fa73f508..45b58c0cd 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,38 +1,28 @@ package guru.sfg.brewery.config; -import guru.sfg.brewery.services.security.JpaUserDetailsService; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +/** + * Created by jt on 6/13/20. + */ @Configuration -@RequiredArgsConstructor @EnableWebSecurity -@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) +@EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { - private final JpaUserDetailsService jpaUserDetailsService; - + // needed for use with Spring Data JPA SPeL @Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { return new SecurityEvaluationContextExtension(); } - public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager) { - RestHeaderAuthFilter filter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**")); - filter.setAuthenticationManager(authenticationManager); - return filter; - } - @Override protected void configure(HttpSecurity http) throws Exception { @@ -41,24 +31,45 @@ protected void configure(HttpSecurity http) throws Exception { authorize .antMatchers("/h2-console/**").permitAll() //do not use in production! .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); - }) + } ) .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic() - .and().csrf().disable(); + .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**"); //h2 console config http.headers().frameOptions().sameOrigin(); } -// @Override + @Bean + PasswordEncoder passwordEncoder(){ + return SfgPasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + // @Override + // protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // auth.userDetailsService(this.jpaUserDetailsService).passwordEncoder(passwordEncoder()); + +// auth.inMemoryAuthentication() +// .withUser("spring") +// .password("{bcrypt}$2a$10$7tYAvVL2/KwcQTcQywHIleKueg4ZK7y7d44hKyngjTwHCDlesxdla") +// .roles("ADMIN") +// .and() +// .withUser("user") +// .password("{sha256}1296cefceb47413d3fb91ac7586a4625c33937b4d3109f5a4dd96c79c46193a029db713b96006ded") +// .roles("USER"); +// +// auth.inMemoryAuthentication().withUser("scott").password("{bcrypt15}$2a$15$baOmQtw8UqWZRDQhMFPFj.xhkkWveCTQHe4OBdr8yw8QshejiSbI6").roles("CUSTOMER"); + // } + + // @Override // @Bean // protected UserDetailsService userDetailsService() { // UserDetails admin = User.withDefaultPasswordEncoder() // .username("spring") -// .password("password") +// .password("guru") // .roles("ADMIN") // .build(); // @@ -68,34 +79,21 @@ protected void configure(HttpSecurity http) throws Exception { // .roles("USER") // .build(); // -// UserDetails customer = User.withDefaultPasswordEncoder() -// .username("scott") -// .password("tiger") -// .roles("CUSTOMER") -// .build(); -// -// return new InMemoryUserDetailsManager(admin, user, customer); +// return new InMemoryUserDetailsManager(admin, user); // } - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(this.jpaUserDetailsService).passwordEncoder(passwordEncoder()); - } -// auth.inMemoryAuthentication() -// .withUser("spring") -// .password("{bcrypt}$2a$10$7tYAvVL2/KwcQTcQywHIleKueg4ZK7y7d44hKyngjTwHCDlesxdla") -// .roles("ADMIN") -// .and() -// .withUser("user") -// .password("{sha256}1296cefceb47413d3fb91ac7586a4625c33937b4d3109f5a4dd96c79c46193a029db713b96006ded") -// .roles("USER"); -// -// auth.inMemoryAuthentication().withUser("scott").password("{bcrypt10}$2a$10$jv7rEbL65k4Q3d/mqG5MLuLDLTlg5oKoq2QOOojfB3e2awo.nlmgu").roles("CUSTOMER"); -// } - @Bean - PasswordEncoder passwordEncoder() { - return SfgPasswordEncoderFactories.createDelegatingPasswordEncoder(); - } -} + + + + + + + + + + + + +} \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java index 2abfb379e..0295a6daf 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerTest.java @@ -1,6 +1,5 @@ package guru.sfg.brewery.web.controllers.api; - import com.fasterxml.jackson.databind.ObjectMapper; import guru.sfg.brewery.bootstrap.DefaultBreweryLoader; import guru.sfg.brewery.domain.Beer; @@ -13,7 +12,6 @@ import guru.sfg.brewery.web.model.BeerOrderDto; import guru.sfg.brewery.web.model.BeerOrderLineDto; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -25,8 +23,7 @@ import java.util.List; import java.util.UUID; -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.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -189,24 +186,44 @@ void getByOrderIdCustomerNOTAuth() throws Exception { .andExpect(status().isForbidden()); } - @Disabled + @Transactional @Test - void pickUpOrderNotAuth() { + void pickUpOrderNotAuth() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(put(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId() + "/pickup")) + .andExpect(status().isUnauthorized()); + } - @Disabled + @Transactional + @WithUserDetails("spring") @Test - void pickUpOrderNotAdminUser() { + void pickUpOrderAdminUser() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(put(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId() + "/pickup")) + .andExpect(status().isNoContent()); } - @Disabled + @Transactional + @WithUserDetails(DefaultBreweryLoader.STPETE_USER) @Test - void pickUpOrderCustomerUserAUTH() { + void pickUpOrderCustomerUserAUTH() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(put(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId() + "/pickup")) + .andExpect(status().isNoContent()); } - @Disabled + @Transactional + @WithUserDetails(DefaultBreweryLoader.DUNEDIN_USER) @Test - void pickUpOrderCustomerUserNOT_AUTH() { + void pickUpOrderCustomerUserNOT_AUTH() throws Exception { + BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); + + mockMvc.perform(put(API_ROOT + stPeteCustomer.getId() + "/orders/" + beerOrder.getId() + "/pickup")) + .andExpect(status().isForbidden()); } private BeerOrderDto buildOrderDto(Customer customer, UUID beerId) { diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java index fe6298ae7..8df259754 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerOrderControllerV2Test.java @@ -10,7 +10,6 @@ import guru.sfg.brewery.repositories.CustomerRepository; import guru.sfg.brewery.web.controllers.BaseIT; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -45,12 +44,9 @@ class BeerOrderControllerV2Test extends BaseIT { @BeforeEach void setUp() { - stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING) - .orElseThrow(); - dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING) - .orElseThrow(); - keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS) - .orElseThrow(); + stPeteCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.ST_PETE_DISTRIBUTING).orElseThrow(); + dunedinCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.DUNEDIN_DISTRIBUTING).orElseThrow(); + keyWestCustomer = customerRepository.findAllByCustomerName(DefaultBreweryLoader.KEY_WEST_DISTRIBUTORS).orElseThrow(); loadedBeers = beerRepository.findAll(); } @@ -77,10 +73,11 @@ void listOrdersCustomerAuth() throws Exception { @WithUserDetails(value = DefaultBreweryLoader.DUNEDIN_USER) @Test void listOrdersCustomerDunedinAuth() throws Exception { - mockMvc.perform(get(API_ROOT)) + mockMvc.perform(get(API_ROOT )) .andExpect(status().isOk()); } + @Transactional @Test void getByOrderIdNotAuth() throws Exception { @@ -90,7 +87,6 @@ void getByOrderIdNotAuth() throws Exception { .andExpect(status().isUnauthorized()); } - @Transactional @WithUserDetails("spring") @Test @@ -118,6 +114,6 @@ void getByOrderIdCustomerNOTAuth() throws Exception { BeerOrder beerOrder = stPeteCustomer.getBeerOrders().stream().findFirst().orElseThrow(); mockMvc.perform(get(API_ROOT + beerOrder.getId())) - .andExpect(status().isForbidden()); + .andExpect(status().isNotFound()); } } \ No newline at end of file diff --git a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java index 79f66db74..b89d51f8d 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/api/BeerRestControllerIT.java @@ -122,4 +122,4 @@ void findBeerByUpcAUTH(String user, String pwd) throws Exception { .andExpect(status().isOk()); } } -} +} \ No newline at end of file From b414d966b1e31f0fc760bed43756c75cf013d7fe Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 17:51:43 +0100 Subject: [PATCH 22/30] updated template --- src/main/resources/templates/index.html | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 42684fb75..991721138 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -13,7 +13,6 @@ ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . - --> @@ -25,6 +24,20 @@

SFG Brewery Monolith

+
+
+ +

Wrong Username or password

+
+ + + + + +
+
+
+
@@ -32,4 +45,4 @@

SFG Brewery Monolith

- + \ No newline at end of file From 1840d47e880abe66a8c8af0e859de91602964498 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:01:08 +0100 Subject: [PATCH 23/30] updated template about logout --- src/main/resources/templates/index.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 991721138..83686d7a4 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -16,7 +16,9 @@ --> - + SFG Brewery @@ -31,12 +33,19 @@

SFG Brewery Monolith

- +
+
+
+

User

+

Logout

+
+
+
From 5022815c60a58cb83b866eb64a808ced20d93571 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:06:09 +0100 Subject: [PATCH 24/30] added configuration for login and logout --- .../guru/sfg/brewery/config/SecurityConfig.java | 15 ++++++++++++++- src/main/resources/templates/index.html | 7 +++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 45b58c0cd..4055aeec0 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** * Created by jt on 6/13/20. @@ -35,7 +36,19 @@ protected void configure(HttpSecurity http) throws Exception { .authorizeRequests() .anyRequest().authenticated() .and() - .formLogin().and() + .formLogin(loginConfigurer -> { + loginConfigurer + .loginProcessingUrl("/login") + .loginPage("/").permitAll() + .successForwardUrl("/") + .defaultSuccessUrl("/"); + }) + .logout(logoutConfigurer -> { + logoutConfigurer + .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) + .logoutSuccessUrl("/") + .permitAll(); + }) .httpBasic() .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**"); diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 83686d7a4..3df5e76ad 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -26,9 +26,8 @@

SFG Brewery Monolith

-
+
-

Wrong Username or password

@@ -39,9 +38,9 @@

SFG Brewery Monolith

-
+
-

User

+

User

Logout

From f0b0520dbfa60a077f0bb4f5f580551bcba2bda5 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:17:28 +0100 Subject: [PATCH 25/30] added eeror message handling --- src/main/java/guru/sfg/brewery/config/SecurityConfig.java | 5 +++-- src/main/resources/templates/index.html | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 4055aeec0..2f54f2598 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -41,12 +41,13 @@ protected void configure(HttpSecurity http) throws Exception { .loginProcessingUrl("/login") .loginPage("/").permitAll() .successForwardUrl("/") - .defaultSuccessUrl("/"); + .defaultSuccessUrl("/") + .failureUrl("/?error"); }) .logout(logoutConfigurer -> { logoutConfigurer .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) - .logoutSuccessUrl("/") + .logoutSuccessUrl("/?logout") .permitAll(); }) .httpBasic() diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 3df5e76ad..9e23771a2 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -28,7 +28,8 @@

SFG Brewery Monolith

-

Wrong Username or password

+
Invalid Username or Password
+
You Have Logged Out
From 646963c48b9579ba967a913173c76e18cd1c30ac Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:20:50 +0100 Subject: [PATCH 26/30] added authority check --- src/main/resources/templates/beers/findBeers.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/beers/findBeers.html b/src/main/resources/templates/beers/findBeers.html index ce0bf55c1..de7bdc98f 100644 --- a/src/main/resources/templates/beers/findBeers.html +++ b/src/main/resources/templates/beers/findBeers.html @@ -1,4 +1,5 @@ @@ -29,7 +30,7 @@

Find Beers


-Add Beer +Add Beer - + \ No newline at end of file From be77efe3a9a76539458ae79e8920fdd170ec0506 Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:36:57 +0100 Subject: [PATCH 27/30] remember me configuration --- .../java/guru/sfg/brewery/config/SecurityConfig.java | 10 +++++++++- src/main/resources/templates/index.html | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 2f54f2598..c5164a83b 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,11 +1,13 @@ package guru.sfg.brewery.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.EnableWebSecurity; 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.data.repository.query.SecurityEvaluationContextExtension; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -13,11 +15,14 @@ /** * Created by jt on 6/13/20. */ +@RequiredArgsConstructor @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final UserDetailsService userDetailsService; + // needed for use with Spring Data JPA SPeL @Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { @@ -51,7 +56,10 @@ protected void configure(HttpSecurity http) throws Exception { .permitAll(); }) .httpBasic() - .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**"); + .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**") + .and().rememberMe() + .key("sfg-key") + .userDetailsService(userDetailsService); //h2 console config http.headers().frameOptions().sameOrigin(); diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 9e23771a2..1dfcbeb0a 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -36,6 +36,8 @@

SFG Brewery Monolith

+ +
From 8031d497ecf9d9401a4013a5fa92fce82b4f0f8b Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Wed, 13 Jan 2021 18:44:01 +0100 Subject: [PATCH 28/30] remember me configuration --- .../sfg/brewery/config/SecurityBeans.java | 23 +++++++++++++++++++ .../sfg/brewery/config/SecurityConfig.java | 22 +++++++----------- src/main/resources/schema.sql | 4 ++++ 3 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/config/SecurityBeans.java create mode 100644 src/main/resources/schema.sql diff --git a/src/main/java/guru/sfg/brewery/config/SecurityBeans.java b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java new file mode 100644 index 000000000..5ec6a7222 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java @@ -0,0 +1,23 @@ +package guru.sfg.brewery.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; + +import javax.sql.DataSource; + +/** + * Created by jt on 7/17/20. + */ +@Configuration +public class SecurityBeans { + + @Bean + public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){ + JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); + tokenRepository.setDataSource(dataSource); + return tokenRepository; + } + +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index c5164a83b..3680ca160 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,5 +1,6 @@ package guru.sfg.brewery.config; + import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,6 +11,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** @@ -22,6 +24,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService; + private final PersistentTokenRepository persistentTokenRepository; // needed for use with Spring Data JPA SPeL @Bean @@ -58,9 +61,13 @@ protected void configure(HttpSecurity http) throws Exception { .httpBasic() .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**") .and().rememberMe() - .key("sfg-key") + .tokenRepository(persistentTokenRepository) .userDetailsService(userDetailsService); + //.rememberMe() + //.key("sfg-key") + //.userDetailsService(userDetailsService); + //h2 console config http.headers().frameOptions().sameOrigin(); } @@ -105,17 +112,4 @@ PasswordEncoder passwordEncoder(){ // } - - - - - - - - - - - - - } \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..012fb1f3a --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,4 @@ +create table persistent_logins (username varchar(64) not null, + series varchar(64) primary key, + token varchar(64) not null, + last_used timestamp not null); \ No newline at end of file From b15c53d5103d526f75257bafe9f4e9cbd39fb0fd Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 14 Jan 2021 11:31:10 +0100 Subject: [PATCH 29/30] added success and failure authentication events --- .../sfg/brewery/config/SecurityBeans.java | 10 ++- .../brewery/domain/security/Authority.java | 6 +- .../brewery/domain/security/LoginFailure.java | 37 +++++++++ .../brewery/domain/security/LoginSuccess.java | 36 +++++++++ .../sfg/brewery/domain/security/Role.java | 13 ++-- .../sfg/brewery/domain/security/User.java | 45 +++++------ .../AuthenticationFailureListener.java | 78 +++++++++++++++++++ .../AuthenticationSuccessListener.java | 56 +++++++++++++ .../security/AuthorityRepository.java | 5 +- .../security/LoginFailureRepository.java | 16 ++++ .../security/LoginSuccessRepository.java | 10 +++ .../repositories/security/RoleRepository.java | 4 +- .../repositories/security/UserRepository.java | 4 +- 13 files changed, 286 insertions(+), 34 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java create mode 100644 src/main/java/guru/sfg/brewery/domain/security/LoginSuccess.java create mode 100644 src/main/java/guru/sfg/brewery/listener/AuthenticationFailureListener.java create mode 100644 src/main/java/guru/sfg/brewery/listener/AuthenticationSuccessListener.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/LoginFailureRepository.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/LoginSuccessRepository.java diff --git a/src/main/java/guru/sfg/brewery/config/SecurityBeans.java b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java index 5ec6a7222..8d662a50b 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityBeans.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java @@ -1,7 +1,10 @@ package guru.sfg.brewery.config; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; @@ -14,10 +17,15 @@ public class SecurityBeans { @Bean - public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){ + public PersistentTokenRepository persistentTokenRepository(DataSource dataSource) { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); return tokenRepository; } + @Bean + public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + return new DefaultAuthenticationEventPublisher(applicationEventPublisher); + } + } \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/Authority.java b/src/main/java/guru/sfg/brewery/domain/security/Authority.java index f0ab96d78..7209abee8 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/Authority.java +++ b/src/main/java/guru/sfg/brewery/domain/security/Authority.java @@ -8,12 +8,12 @@ /** * Created by jt on 6/21/20. */ -@Entity @Setter @Getter -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor @Builder +@Entity public class Authority { @Id @@ -24,4 +24,4 @@ public class Authority { @ManyToMany(mappedBy = "authorities") private Set roles; -} \ No newline at end of file +} diff --git a/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java b/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java new file mode 100644 index 000000000..554720a5b --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java @@ -0,0 +1,37 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.sql.Timestamp; + +/** + * Created by jt on 7/20/20. + */ +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@Entity +public class LoginFailure { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private String username; + + @ManyToOne + private User user; + + private String sourceIp; + + @CreationTimestamp + @Column(updatable = false) + private Timestamp createdDate; + + @UpdateTimestamp + private Timestamp lastModifiedDate; +} diff --git a/src/main/java/guru/sfg/brewery/domain/security/LoginSuccess.java b/src/main/java/guru/sfg/brewery/domain/security/LoginSuccess.java new file mode 100644 index 000000000..2676f9b88 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/LoginSuccess.java @@ -0,0 +1,36 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.sql.Timestamp; + +/** + * Created by jt on 7/20/20. + */ +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@Entity +public class LoginSuccess { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + @ManyToOne + private User user; + + private String sourceIp; + + @CreationTimestamp + @Column(updatable = false) + private Timestamp createdDate; + + @UpdateTimestamp + private Timestamp lastModifiedDate; +} diff --git a/src/main/java/guru/sfg/brewery/domain/security/Role.java b/src/main/java/guru/sfg/brewery/domain/security/Role.java index a862a8e1a..5c4ffe1f2 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/Role.java +++ b/src/main/java/guru/sfg/brewery/domain/security/Role.java @@ -5,14 +5,16 @@ import javax.persistence.*; import java.util.Set; -@Entity +/** + * Created by jt on 6/29/20. + */ @Setter @Getter -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor @Builder +@Entity public class Role { - @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @@ -25,7 +27,8 @@ public class Role { @Singular @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER) @JoinTable(name = "role_authority", - joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}, - inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) + joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) private Set authorities; + } diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index a159652c5..b4fcee270 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -14,12 +14,12 @@ /** * Created by jt on 6/21/20. */ -@Entity @Setter @Getter -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor @Builder +@Entity public class User implements UserDetails, CredentialsContainer { @Id @@ -29,38 +29,27 @@ public class User implements UserDetails, CredentialsContainer { private String username; private String password; - @ManyToOne(fetch = FetchType.EAGER) - private Customer customer; - @Singular @ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", - joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, - inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) + joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) private Set roles; + @ManyToOne(fetch = FetchType.EAGER) + private Customer customer; + @Transient public Set getAuthorities() { return this.roles.stream() .map(Role::getAuthorities) .flatMap(Set::stream) - .map(auth -> new SimpleGrantedAuthority(auth.getPermission())) + .map(authority -> { + return new SimpleGrantedAuthority(authority.getPermission()); + }) .collect(Collectors.toSet()); } - @Builder.Default - private Boolean accountNonExpired = true; - - @Builder.Default - private Boolean accountNonLocked = true; - - @Builder.Default - private Boolean credentialsNonExpired = true; - - @Builder.Default - private Boolean enabled = true; - - @Override public boolean isAccountNonExpired() { return this.accountNonExpired; @@ -81,8 +70,20 @@ public boolean isEnabled() { return this.enabled; } + @Builder.Default + private Boolean accountNonExpired = true; + + @Builder.Default + private Boolean accountNonLocked = true; + + @Builder.Default + private Boolean credentialsNonExpired = true; + + @Builder.Default + private Boolean enabled = true; + @Override public void eraseCredentials() { this.password = null; } -} \ No newline at end of file +} diff --git a/src/main/java/guru/sfg/brewery/listener/AuthenticationFailureListener.java b/src/main/java/guru/sfg/brewery/listener/AuthenticationFailureListener.java new file mode 100644 index 000000000..a34414ce2 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/listener/AuthenticationFailureListener.java @@ -0,0 +1,78 @@ +package guru.sfg.brewery.listener; + +import guru.sfg.brewery.domain.security.LoginFailure; +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.LoginFailureRepository; +import guru.sfg.brewery.repositories.security.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +/** + * Created by jt on 7/20/20. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class AuthenticationFailureListener { + + private final LoginFailureRepository loginFailureRepository; + private final UserRepository userRepository; + + @EventListener + public void listen(AuthenticationFailureBadCredentialsEvent event) { + log.debug("Login failure"); + + if (event.getSource() instanceof UsernamePasswordAuthenticationToken) { + UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) event.getSource(); + LoginFailure.LoginFailureBuilder builder = LoginFailure.builder(); + + if (token.getPrincipal() instanceof String) { + log.debug("Attempted Username: " + token.getPrincipal()); + builder.username((String) token.getPrincipal()); + userRepository.findByUsername((String) token.getPrincipal()).ifPresent(builder::user); + } + + if (token.getDetails() instanceof WebAuthenticationDetails) { + WebAuthenticationDetails details = (WebAuthenticationDetails) token.getDetails(); + + log.debug("Source IP: " + details.getRemoteAddress()); + builder.sourceIp(details.getRemoteAddress()); + } + LoginFailure failure = loginFailureRepository.save(builder.build()); + log.debug("Failure Event: " + failure.getId()); + + if (failure.getUser() != null) { + lockUserAccount(failure.getUser()); + } + } + + + } + + private void lockUserAccount(User user) { + List failures = loginFailureRepository.findAllByUserAndCreatedDateIsAfter(user, + Timestamp.valueOf(LocalDateTime.now().minusDays(1))); + + if(failures.size() > 3){ + log.debug("Locking User Account... "); + user.setAccountNonLocked(false); + userRepository.save(user); + } + } + + + + + + + +} diff --git a/src/main/java/guru/sfg/brewery/listener/AuthenticationSuccessListener.java b/src/main/java/guru/sfg/brewery/listener/AuthenticationSuccessListener.java new file mode 100644 index 000000000..73d278389 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/listener/AuthenticationSuccessListener.java @@ -0,0 +1,56 @@ +package guru.sfg.brewery.listener; + +import guru.sfg.brewery.domain.security.LoginSuccess; +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.LoginSuccessRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +/** + * Created by jt on 7/18/20. + */ +@Slf4j +@RequiredArgsConstructor +@Component +public class AuthenticationSuccessListener { + + private final LoginSuccessRepository loginSuccessRepository; + + @EventListener + public void listen(AuthenticationSuccessEvent event){ + + log.debug("User Logged In Okay"); + + if (event.getSource() instanceof UsernamePasswordAuthenticationToken) { + LoginSuccess.LoginSuccessBuilder builder = LoginSuccess.builder(); + + UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) event.getSource(); + + if(token.getPrincipal() instanceof User){ + User user = (User) token.getPrincipal(); + builder.user(user); + + log.debug("User name logged in: " + user.getUsername() ); + } + + if(token.getDetails() instanceof WebAuthenticationDetails){ + WebAuthenticationDetails details = (WebAuthenticationDetails) token.getDetails(); + + log.debug("Source IP: " + details.getRemoteAddress()); + builder.sourceIp(details.getRemoteAddress()); + } + + LoginSuccess loginSuccess = loginSuccessRepository.save(builder.build()); + + log.debug("Login Success saved. Id: " + loginSuccess.getId()); + } + + + + } +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java index 20ddc1565..86cf4efa0 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java @@ -3,5 +3,8 @@ import guru.sfg.brewery.domain.security.Authority; import org.springframework.data.jpa.repository.JpaRepository; +/** + * Created by jt on 6/21/20. + */ public interface AuthorityRepository extends JpaRepository { -} \ No newline at end of file +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/LoginFailureRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/LoginFailureRepository.java new file mode 100644 index 000000000..f192bc89a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/LoginFailureRepository.java @@ -0,0 +1,16 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.LoginFailure; +import guru.sfg.brewery.domain.security.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.sql.Timestamp; +import java.util.List; + +/** + * Created by jt on 7/20/20. + */ +public interface LoginFailureRepository extends JpaRepository { + + List findAllByUserAndCreatedDateIsAfter(User user, Timestamp timestamp); +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/LoginSuccessRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/LoginSuccessRepository.java new file mode 100644 index 000000000..b196113aa --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/LoginSuccessRepository.java @@ -0,0 +1,10 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.LoginSuccess; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Created by jt on 7/20/20. + */ +public interface LoginSuccessRepository extends JpaRepository { +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java index 02616bb65..88ad96ccf 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java @@ -5,7 +5,9 @@ import java.util.Optional; +/** + * Created by jt on 6/29/20. + */ public interface RoleRepository extends JpaRepository { - Optional findByName(String customer); } diff --git a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java index 5a27e7802..101dbd4b5 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java @@ -5,7 +5,9 @@ import java.util.Optional; +/** + * Created by jt on 6/21/20. + */ public interface UserRepository extends JpaRepository { - Optional findByUsername(String username); } From ab4c4e3530dd45c39dfca1235d8055603c398ecd Mon Sep 17 00:00:00 2001 From: skrzeminski Date: Thu, 14 Jan 2021 12:35:44 +0100 Subject: [PATCH 30/30] added shecguling to unlock accounts --- .../guru/sfg/brewery/config/TaskConfig.java | 21 +++++++++++ .../brewery/domain/security/LoginFailure.java | 2 +- .../sfg/brewery/domain/security/User.java | 16 ++++++-- .../repositories/security/UserRepository.java | 9 +++-- .../brewery/services/UserUnlockService.java | 37 +++++++++++++++++++ src/main/resources/templates/index.html | 6 ++- 6 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/config/TaskConfig.java create mode 100644 src/main/java/guru/sfg/brewery/services/UserUnlockService.java diff --git a/src/main/java/guru/sfg/brewery/config/TaskConfig.java b/src/main/java/guru/sfg/brewery/config/TaskConfig.java new file mode 100644 index 000000000..92ece6b5e --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/TaskConfig.java @@ -0,0 +1,21 @@ +package guru.sfg.brewery.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Task Configuration - enable asyc tasks + */ +@EnableScheduling +@EnableAsync +@Configuration +public class TaskConfig { + @Bean + TaskExecutor taskExecutor() { + return new SimpleAsyncTaskExecutor(); + } +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java b/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java index 554720a5b..dd7710f69 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java +++ b/src/main/java/guru/sfg/brewery/domain/security/LoginFailure.java @@ -34,4 +34,4 @@ public class LoginFailure { @UpdateTimestamp private Timestamp lastModifiedDate; -} +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java index b4fcee270..b480bbc55 100644 --- a/src/main/java/guru/sfg/brewery/domain/security/User.java +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -2,12 +2,15 @@ import guru.sfg.brewery.domain.Customer; import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; +import java.sql.Timestamp; import java.util.Set; import java.util.stream.Collectors; @@ -32,8 +35,8 @@ public class User implements UserDetails, CredentialsContainer { @Singular @ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", - joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, - inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) + joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")}) private Set roles; @ManyToOne(fetch = FetchType.EAGER) @@ -86,4 +89,11 @@ public boolean isEnabled() { public void eraseCredentials() { this.password = null; } -} + + @CreationTimestamp + @Column(updatable = false) + private Timestamp createdDate; + + @UpdateTimestamp + private Timestamp lastModifiedDate; +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java index 101dbd4b5..7a26122c3 100644 --- a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java +++ b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java @@ -3,11 +3,12 @@ import guru.sfg.brewery.domain.security.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.sql.Timestamp; +import java.util.List; import java.util.Optional; -/** - * Created by jt on 6/21/20. - */ public interface UserRepository extends JpaRepository { Optional findByUsername(String username); -} + + List findAllByAccountNonLockedAndLastModifiedDateIsBefore(Boolean locked, Timestamp timestamp); +} \ No newline at end of file diff --git a/src/main/java/guru/sfg/brewery/services/UserUnlockService.java b/src/main/java/guru/sfg/brewery/services/UserUnlockService.java new file mode 100644 index 000000000..6991586ac --- /dev/null +++ b/src/main/java/guru/sfg/brewery/services/UserUnlockService.java @@ -0,0 +1,37 @@ +package guru.sfg.brewery.services; + +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@Service +public class UserUnlockService { + + private final UserRepository userRepository; + + @Scheduled(fixedRate = 5000) + public void unlockAccounts(){ + log.debug("Running Unlock Accounts"); + + List lockedUsers = userRepository + .findAllByAccountNonLockedAndLastModifiedDateIsBefore(false, + Timestamp.valueOf(LocalDateTime.now().minusSeconds(30))); + + if(lockedUsers.size() > 0){ + log.debug("Locked Accounts Found, Unlocking"); + lockedUsers.forEach(user -> user.setAccountNonLocked(true)); + + userRepository.saveAll(lockedUsers); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 1dfcbeb0a..fbaa0237f 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -28,7 +28,10 @@

SFG Brewery Monolith

-
Invalid Username or Password
+
Invalid Username or Password
+ +
User Account is Locked.
+
You Have Logged Out
@@ -48,6 +51,7 @@

SFG Brewery Monolith

+