diff --git a/backend/pom.xml b/backend/pom.xml
index d73dc84..9d8a562 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -97,16 +97,16 @@
-
-
-
-
+
+ org.springframework.boot
+ spring-boot-starter-security
+
-
-
-
-
-
+
+ org.springframework.security
+ spring-security-test
+ test
+
diff --git a/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java b/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java
index 002909c..d71b522 100644
--- a/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java
+++ b/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java
@@ -27,7 +27,10 @@ public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/**").allowedOrigins("http://localhost:8080");
+ registry
+ .addMapping("/**")
+ .allowedOrigins("http://localhost:9000")
+ .allowedMethods("OPTIONS", "PUT", "DELETE", "GET", "POST", "PATCH");
}
};
}
diff --git a/backend/src/main/java/com/tartu/library/LibraryApplication.java b/backend/src/main/java/com/tartu/library/LibraryApplication.java
index 268fb3c..86718e9 100644
--- a/backend/src/main/java/com/tartu/library/LibraryApplication.java
+++ b/backend/src/main/java/com/tartu/library/LibraryApplication.java
@@ -2,11 +2,12 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class LibraryApplication {
public static void main(String[] args) {
- SpringApplication.run(LibraryApplication.class, args);
+ ConfigurableApplicationContext ctx = SpringApplication.run(LibraryApplication.class, args);
}
}
diff --git a/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java
new file mode 100644
index 0000000..bc8aa89
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java
@@ -0,0 +1,57 @@
+package com.tartu.library.auth.domain.model;
+
+import com.tartu.library.person.domain.model.Person;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import javax.persistence.*;
+import java.util.Collection;
+import java.util.Set;
+import java.util.UUID;
+
+@Entity
+@Data
+@NoArgsConstructor(force = true, access = AccessLevel.PROTECTED)
+public class AuthUser implements UserDetails {
+ /** User is reserved word in Postgres */
+ String username;
+ String password;
+ @OneToOne Person person;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private UUID id;
+
+ @Transient private String passwordConfirm;
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ private Set roles;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return roles;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java b/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java
new file mode 100644
index 0000000..e37df60
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java
@@ -0,0 +1,35 @@
+package com.tartu.library.auth.domain.model;
+
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+
+import javax.persistence.*;
+import java.util.Set;
+
+@Entity
+@NoArgsConstructor
+public class Role implements GrantedAuthority {
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Id
+ Long id;
+
+ private String name;
+
+ @Transient
+ @ManyToMany(mappedBy = "roles")
+ private Set people;
+
+ public Role(Long id) {
+ this.id = id;
+ }
+
+ public Role(Long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ @Override
+ public String getAuthority() {
+ return name;
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java b/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java
new file mode 100644
index 0000000..897276e
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java
@@ -0,0 +1,8 @@
+package com.tartu.library.auth.domain.repository;
+
+import com.tartu.library.auth.domain.model.AuthUser;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AuthUserRepository extends JpaRepository {
+ AuthUser findByUsername(String username);
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java b/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java
new file mode 100644
index 0000000..1337c7a
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java
@@ -0,0 +1,6 @@
+package com.tartu.library.auth.domain.repository;
+
+import com.tartu.library.auth.domain.model.Role;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface RoleRepository extends JpaRepository {}
diff --git a/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java b/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java
new file mode 100644
index 0000000..07ab1db
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java
@@ -0,0 +1,25 @@
+package com.tartu.library.auth.rest;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+public class AuthenticationController {
+ @GetMapping("/api/authenticate")
+ public List authenticate() {
+ Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ List roles = new ArrayList<>();
+ if (principal instanceof UserDetails) {
+ UserDetails details = (UserDetails) principal;
+ for (GrantedAuthority authority : details.getAuthorities())
+ roles.add(authority.getAuthority());
+ }
+ return roles;
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/rest/UserController.java b/backend/src/main/java/com/tartu/library/auth/rest/UserController.java
new file mode 100644
index 0000000..905b3e3
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/rest/UserController.java
@@ -0,0 +1,20 @@
+package com.tartu.library.auth.rest;
+
+import com.tartu.library.auth.domain.model.AuthUser;
+import com.tartu.library.auth.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+ @Autowired UserService userService;
+
+ @PostMapping()
+ public void createUser(@RequestBody AuthUser authUser) {
+ userService.save(authUser);
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java
new file mode 100644
index 0000000..2b64baa
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java
@@ -0,0 +1,48 @@
+package com.tartu.library.auth.security;
+
+import com.tartu.library.auth.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+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.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+@Configuration
+@EnableGlobalMethodSecurity(securedEnabled = true)
+public class SecurityConfigurator extends WebSecurityConfigurerAdapter {
+
+ @Qualifier("userService")
+ @Autowired
+ UserService userService;
+
+ @Bean
+ public BCryptPasswordEncoder bCryptPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf()
+ .disable()
+ .cors()
+ .and()
+ .authorizeRequests()
+ .antMatchers("/h2-console/**").permitAll()
+ .antMatchers("/api/users").permitAll()
+ .antMatchers("/api/authenticate").permitAll()
+ .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
+ .antMatchers("/api/**").authenticated()
+ .and().httpBasic();
+ http.headers().frameOptions().disable();
+ }
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder());
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/auth/service/UserService.java b/backend/src/main/java/com/tartu/library/auth/service/UserService.java
new file mode 100644
index 0000000..8ce5398
--- /dev/null
+++ b/backend/src/main/java/com/tartu/library/auth/service/UserService.java
@@ -0,0 +1,35 @@
+package com.tartu.library.auth.service;
+
+import com.tartu.library.auth.domain.model.AuthUser;
+import com.tartu.library.auth.domain.repository.AuthUserRepository;
+import com.tartu.library.auth.domain.repository.RoleRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashSet;
+
+@Service
+public class UserService implements UserDetailsService {
+ @Autowired BCryptPasswordEncoder bCryptPasswordEncoder;
+ @Autowired RoleRepository roleRepository;
+ @Autowired AuthUserRepository authUserRepository;
+
+ public void save(AuthUser authUser) {
+ authUser.setPassword(bCryptPasswordEncoder.encode(authUser.getPassword()));
+ authUser.setRoles(new HashSet<>(roleRepository.findAll()));
+ authUserRepository.save(authUser);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ AuthUser authUser = authUserRepository.findByUsername(username);
+ if (authUser == null) throw new UsernameNotFoundException(username);
+ return authUser;
+ }
+}
diff --git a/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java b/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java
index bfd2467..fda7c9d 100644
--- a/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java
+++ b/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java
@@ -9,6 +9,7 @@
import org.springframework.hateoas.CollectionModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -40,6 +41,7 @@ public BookEntryDTO retrieveBook(@PathVariable UUID uuid) {
}
@PatchMapping("{uuid}")
+ @Secured({"ADMIN"})
public ResponseEntity modifyBook(@PathVariable UUID uuid) {
logger.info(String.format("Modifying Book Entry (%s)", uuid.toString()));
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
diff --git a/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java b/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java
index 77d36d8..fea6767 100644
--- a/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java
+++ b/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java
@@ -10,6 +10,7 @@
import org.springframework.hateoas.CollectionModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@@ -35,6 +36,7 @@ public BookItemDTO retrieveBookItem(@PathVariable UUID uuid) {
}
@DeleteMapping("{uuid}")
+ @Secured({"ADMIN"})
public ResponseEntity deleteBookItem(@PathVariable UUID uuid) {
logger.info(String.format("Deleting Book Item (%s)", uuid.toString()));
bookService.deleteBookItem(uuid);
@@ -42,6 +44,7 @@ public ResponseEntity deleteBookItem(@PathVariable UUID uuid) {
}
@PatchMapping("{uuid}/borrow")
+ @Secured({"AUTH_USER"})
public BookItemDTO borrowBook(@PathVariable UUID uuid, @RequestParam UUID person_uuid)
throws InvalidBookStatusException {
logger.info(String.format("Borrowing Book Item (%s)", uuid.toString()));
@@ -49,12 +52,14 @@ public BookItemDTO borrowBook(@PathVariable UUID uuid, @RequestParam UUID person
}
@PatchMapping("{uuid}/return")
+ @Secured({"AUTH_USER"})
public BookItemDTO returnBook(@PathVariable UUID uuid) throws InvalidBookStatusException {
logger.info(String.format("Returning Book Item (%s)", uuid.toString()));
return bookService.returnBook(uuid);
}
@GetMapping("{uuid}/logs")
+ @Secured({"AUTH_USER"})
public CollectionModel retrieveBorrowLogs(@PathVariable UUID uuid) {
logger.info(String.format("Retrieving Borrow Logs from Book Item (%s)", uuid.toString()));
return bookService.retrieveBorrowLogsByBookItem(uuid);
diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql
new file mode 100644
index 0000000..9093ee2
--- /dev/null
+++ b/backend/src/main/resources/data.sql
@@ -0,0 +1,10 @@
+insert into person (id, name) values (1, 'Admin');
+
+insert into role (id, name) values (1, 'ADMIN');
+insert into role (id, name) values (2, 'AUTH_USER');
+
+insert into auth_user (id, username, password, person_id) values (1, 'admin', '$2a$10$/BokLf7JuxdOZZK5hEUzauPOUnfyiWZ.P3SgnsBaE14FD.kjKd/U2', 1);
+insert into auth_user (id, username, password) values (2, 'user', '$2a$10$vIQpONjeg31v..Dat4L6BO.u.hliQ5e0NrJQU.JMWhA/3R.ZRmZl2');
+
+insert into auth_user_roles (auth_user_id, roles_id) values (1, 1);
+insert into auth_user_roles (auth_user_id, roles_id) values (2, 2);
\ No newline at end of file