diff --git a/pom.xml b/pom.xml
index df7a6cb36..325766c77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,11 +138,19 @@
spring-boot-starter-test
test
+
+ org.springframework.boot
+ spring-boot-starter-security
+
org.springframework.security
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 c8f04894b..6bda19322 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,19 @@
/**
* 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 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";
public static final String BEER_3_UPC = "0083783375213";
@@ -44,14 +62,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_USER)
+ .password(passwordEncoder.encode("password"))
+ .customer(stPeteCustomer)
+ .role(customerRole).build());
+
+ 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)
+ .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 +201,72 @@ 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 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, pickupOrder)));
+
+ customerRole.setAuthorities(new HashSet<>(Set.of(readBeer, readCustomer, readBrewery, createOrderCustomer, readOrderCustomer,
+ updateOrderCustomer, deleteOrderCustomer, pickupOrderCustomer)));
+
+ 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());
+ }
+
}
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/RestHeaderAuthFilter.java b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java
new file mode 100644
index 000000000..5194dee0e
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/config/RestHeaderAuthFilter.java
@@ -0,0 +1,120 @@
+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;
+
+/**
+ * Created by jt on 6/19/20.
+ */
+@Slf4j
+public class RestHeaderAuthFilter extends AbstractAuthenticationProcessingFilter {
+
+ 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) {
+ userName = "";
+ }
+
+ if (password == null) {
+ password = "";
+ }
+
+ log.debug("Authenticating User: " + userName);
+
+ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password);
+
+ 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) {
+ 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/SecurityBeans.java b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java
new file mode 100644
index 000000000..8d662a50b
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/config/SecurityBeans.java
@@ -0,0 +1,31 @@
+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;
+
+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;
+ }
+
+ @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/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java
new file mode 100644
index 000000000..3680ca160
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java
@@ -0,0 +1,115 @@
+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.authentication.rememberme.PersistentTokenRepository;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+/**
+ * Created by jt on 6/13/20.
+ */
+@RequiredArgsConstructor
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private final UserDetailsService userDetailsService;
+ private final PersistentTokenRepository persistentTokenRepository;
+
+ // needed for use with Spring Data JPA SPeL
+ @Bean
+ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
+ return new SecurityEvaluationContextExtension();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http
+ .authorizeRequests(authorize -> {
+ authorize
+ .antMatchers("/h2-console/**").permitAll() //do not use in production!
+ .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll();
+ } )
+ .authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin(loginConfigurer -> {
+ loginConfigurer
+ .loginProcessingUrl("/login")
+ .loginPage("/").permitAll()
+ .successForwardUrl("/")
+ .defaultSuccessUrl("/")
+ .failureUrl("/?error");
+ })
+ .logout(logoutConfigurer -> {
+ logoutConfigurer
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
+ .logoutSuccessUrl("/?logout")
+ .permitAll();
+ })
+ .httpBasic()
+ .and().csrf().ignoringAntMatchers("/h2-console/**", "/api/**")
+ .and().rememberMe()
+ .tokenRepository(persistentTokenRepository)
+ .userDetailsService(userDetailsService);
+
+ //.rememberMe()
+ //.key("sfg-key")
+ //.userDetailsService(userDetailsService);
+
+ //h2 console config
+ http.headers().frameOptions().sameOrigin();
+ }
+
+ @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("guru")
+// .roles("ADMIN")
+// .build();
+//
+// UserDetails user = User.withDefaultPasswordEncoder()
+// .username("user")
+// .password("password")
+// .roles("USER")
+// .build();
+//
+// return new InMemoryUserDetailsManager(admin, user);
+// }
+
+
+}
\ No newline at end of file
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/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/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/Authority.java b/src/main/java/guru/sfg/brewery/domain/security/Authority.java
new file mode 100644
index 000000000..7209abee8
--- /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.
+ */
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+public class Authority {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Integer id;
+
+ private String permission;
+
+ @ManyToMany(mappedBy = "authorities")
+ private Set roles;
+}
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..dd7710f69
--- /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;
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..5c4ffe1f2
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/domain/security/Role.java
@@ -0,0 +1,34 @@
+package guru.sfg.brewery.domain.security;
+
+import lombok.*;
+
+import javax.persistence.*;
+import java.util.Set;
+
+/**
+ * Created by jt on 6/29/20.
+ */
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+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
new file mode 100644
index 000000000..b480bbc55
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/domain/security/User.java
@@ -0,0 +1,99 @@
+package guru.sfg.brewery.domain.security;
+
+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;
+
+/**
+ * Created by jt on 6/21/20.
+ */
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+public class User implements UserDetails, CredentialsContainer {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Integer id;
+
+ private String username;
+ private String password;
+
+ @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")})
+ 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(authority -> {
+ return new SimpleGrantedAuthority(authority.getPermission());
+ })
+ .collect(Collectors.toSet());
+ }
+
+ @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;
+ }
+
+ @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;
+ }
+
+ @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/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/permission/BeerCreatePermission.java b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.java
new file mode 100644
index 000000000..4750ac4de
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/permission/BeerCreatePermission.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 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
new file mode 100644
index 000000000..a8f7d9959
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/permission/BeerDeletePermission.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 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
new file mode 100644
index 000000000..ece56cc43
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/permission/BeerReadPermission.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 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
new file mode 100644
index 000000000..cf2dc28a4
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/permission/BeerUpdatePermission.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 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/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/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..86cf4efa0
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java
@@ -0,0 +1,10 @@
+package guru.sfg.brewery.repositories.security;
+
+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 {
+}
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
new file mode 100644
index 000000000..88ad96ccf
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/repositories/security/RoleRepository.java
@@ -0,0 +1,13 @@
+package guru.sfg.brewery.repositories.security;
+
+import guru.sfg.brewery.domain.security.Role;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+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
new file mode 100644
index 000000000..7a26122c3
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java
@@ -0,0 +1,14 @@
+package guru.sfg.brewery.repositories.security;
+
+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;
+
+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/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/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/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..ff0d429b3
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/services/security/JpaUserDetailsService.java
@@ -0,0 +1,41 @@
+package guru.sfg.brewery.services.security;
+
+import guru.sfg.brewery.domain.security.Authority;
+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 {
+ return userRepository.findByUsername(username).orElseThrow(() -> {
+ return new UsernameNotFoundException("User name: " + username + " not found");
+ });
+ }
+
+ private Collection extends GrantedAuthority> convertToSpringAuthorities(Set authorities) {
+ if (authorities != null && authorities.size() > 0) {
+ return authorities.stream()
+ .map(Authority::getPermission)
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toSet());
+ } else {
+ return new HashSet<>();
+ }
+ }
+}
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/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 06fef9090..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,6 +18,9 @@
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.stereotype.Controller;
@@ -41,14 +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";
}
+ @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() + "%");
@@ -67,7 +72,8 @@ public String processFindFormReturnMany(Customer customer, BindingResult result,
}
}
- @GetMapping("/{customerId}")
+ @CustomerReadPermission
+ @GetMapping("/{customerId}")
public ModelAndView showCustomer(@PathVariable UUID customerId) {
ModelAndView mav = new ModelAndView("customers/customerDetails");
//ToDO: Add Service
@@ -75,12 +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";
}
+ @CustomerCreatePermission
@PostMapping("/new")
public String processCreationForm(Customer customer) {
//ToDO: Add Service
@@ -88,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())
- 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";
+ }
+ @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
new file mode 100644
index 000000000..1a1c42fcf
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerOrderController.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+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;
+ }
+
+ @BeerOrderReadPermission
+ @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));
+ }
+
+ @BeerOrderCreatePermission
+ @PostMapping("orders")
+ @ResponseStatus(HttpStatus.CREATED)
+ 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) {
+ 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) {
+ beerOrderService.pickupOrder(customerId, orderId);
+ }
+}
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/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java b/src/main/java/guru/sfg/brewery/web/controllers/api/BeerRestController.java
index 057ff9a81..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
@@ -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;
@@ -46,6 +50,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 +77,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 +91,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 +111,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 +120,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){
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 912080aaa..13ff89a5d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -20,7 +20,22 @@ 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
-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
+
+logging.level.guru.sfg.brewery=debug
+logging.level.org.springframework.security=debug
\ 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
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
-
+