From e791fee38bd06341535a84b87c15b371239dfa97 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 13 Aug 2023 01:12:29 +0100 Subject: [PATCH] security --- .gitignore | 2 +- build.gradle | 2 + .../software/SPDF/SPdfApplication.java | 5 +- .../stirling/software/SPDF/config/Beans.java | 14 ++ .../SPDF/controller/api/UserController.java | 45 ++++ .../controller/web/GeneralWebController.java | 71 +++++++ .../software/SPDF/model/Authority.java | 14 +- .../stirling/software/SPDF/model/Role.java | 11 + .../stirling/software/SPDF/model/User.java | 41 +++- src/main/resources/application.properties | 6 +- src/main/resources/messages_en_GB.properties | 4 +- src/main/resources/templates/account.html | 194 ++++++++++++++++++ src/main/resources/templates/addUsers.html | 76 +++++++ .../resources/templates/fragments/navbar.html | 48 +---- src/main/resources/templates/home.html | 3 - 15 files changed, 480 insertions(+), 56 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/model/Role.java create mode 100644 src/main/resources/templates/account.html create mode 100644 src/main/resources/templates/addUsers.html diff --git a/.gitignore b/.gitignore index 661ca292..86a25aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -116,7 +116,7 @@ watchedFolders/ *.zip *.tar.gz *.rar - +*.db /build /.vscode \ No newline at end of file diff --git a/build.gradle b/build.gradle index 23fa8fd6..f741a521 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,8 @@ dependencies { implementation group: 'com.google.zxing', name: 'core', version: '3.5.1' // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation 'org.commonmark:commonmark:0.21.0' + // https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core + implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index f374f9b1..5955c339 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -14,9 +14,10 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import jakarta.annotation.PostConstruct; import stirling.software.SPDF.utils.GeneralUtils; - +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @SpringBootApplication -@EnableWebSecurity(debug = true) +@EnableWebSecurity() +@EnableGlobalMethodSecurity(prePostEnabled = true) //@EnableScheduling public class SPdfApplication { diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index f8ff302e..b4aa1ffb 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.config; +import java.time.Duration; import java.util.Locale; import org.springframework.context.annotation.Bean; @@ -10,9 +11,22 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.Refill; + @Configuration public class Beans implements WebMvcConfigurer { + @Bean + public Bucket createRateLimitBucket() { + Refill refill = Refill.of(1000, Duration.ofDays(1)); + Bandwidth limit = Bandwidth.classic(1000, refill).withInitialTokens(1000); + return Bucket4j.builder().addLimit(limit).build(); + } + + @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index 0fdfebb4..6242a458 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -1,12 +1,20 @@ package stirling.software.SPDF.controller.api; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import jakarta.servlet.http.HttpServletRequest; import stirling.software.SPDF.config.security.UserService; @Controller @@ -25,4 +33,41 @@ public class UserController { userService.saveUser(username, password); return "redirect:/login?registered=true"; } + + + @PostMapping("/updateUserSettings") + public String updateUserSettings(HttpServletRequest request, Principal principal) { + Map paramMap = request.getParameterMap(); + Map updates = new HashMap<>(); + + System.out.println("Received parameter map: " + paramMap); + + for (Map.Entry entry : paramMap.entrySet()) { + updates.put(entry.getKey(), entry.getValue()[0]); + } + + System.out.println("Processed updates: " + updates); + + // Assuming you have a method in userService to update the settings for a user + userService.updateUserSettings(principal.getName(), updates); + + return "redirect:/account"; // Redirect to a page of your choice after updating + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PostMapping("/admin/saveUser") + public String saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role) { + userService.saveUser(username, password, role); + return "redirect:/addUsers"; // Redirect to account page after adding the user + } + + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @GetMapping("/admin/deleteUser/{username}") + public String deleteUser(@PathVariable String username) { + userService.deleteUser(username); + return "redirect:/addUsers"; + } + + } diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 7da338ba..c415d8fd 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -4,11 +4,13 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,16 +18,23 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; @Controller @Tag(name = "General", description = "General APIs") public class GeneralWebController { @@ -48,7 +57,69 @@ public class GeneralWebController { return "login"; } + @Autowired + private UserRepository userRepository; // Assuming you have a repository for user operations + + @Autowired + private UserService userService; // Assuming you have a repository for user operations + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @GetMapping("/addUsers") + public String showAddUserForm(Model model) { + List allUsers = userRepository.findAll(); + model.addAttribute("users", allUsers); + return "addUsers"; + } + + + + @GetMapping("/account") + public String account(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + return "redirect:/"; + } + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; + + // Retrieve username and other attributes + String username = userDetails.getUsername(); + + // Fetch user details from the database + Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } + + // Convert settings map to JSON string + ObjectMapper objectMapper = new ObjectMapper(); + String settingsJson; + try { + settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); + } catch (JsonProcessingException e) { + // Handle JSON conversion error + e.printStackTrace(); + return "redirect:/error"; // Example redirection in case of error + } + + // Add attributes to the model + model.addAttribute("username", username); + model.addAttribute("role", user.get().getRolesAsString()); + model.addAttribute("settings", settingsJson); + } + } else { + return "redirect:/"; + } + return "account"; + } + + + @GetMapping("/pipeline") @Hidden public String pipelineForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/model/Authority.java b/src/main/java/stirling/software/SPDF/model/Authority.java index e87ece3f..bb8e1d6e 100644 --- a/src/main/java/stirling/software/SPDF/model/Authority.java +++ b/src/main/java/stirling/software/SPDF/model/Authority.java @@ -13,7 +13,19 @@ import jakarta.persistence.Table; @Table(name = "authorities") public class Authority { - @Id + public Authority() { + + } + + + public Authority(String authority, User user) { + this.authority = authority; + this.user = user; + user.getAuthorities().add(this); + } + + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/stirling/software/SPDF/model/Role.java b/src/main/java/stirling/software/SPDF/model/Role.java new file mode 100644 index 00000000..2d28176f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/Role.java @@ -0,0 +1,11 @@ +package stirling.software.SPDF.model; +public final class Role { + + public static final String ADMIN = "ROLE_ADMIN"; + public static final String USER = "ROLE_USER"; + public static final String LIMITED_API_USER = "ROLE_LIMITED_API_USER"; + public static final String WEB_ONLY_USER = "ROLE_WEB_ONLY_USER"; + + + +} diff --git a/src/main/java/stirling/software/SPDF/model/User.java b/src/main/java/stirling/software/SPDF/model/User.java index 2cea4d73..6b085d0c 100644 --- a/src/main/java/stirling/software/SPDF/model/User.java +++ b/src/main/java/stirling/software/SPDF/model/User.java @@ -1,15 +1,22 @@ package stirling.software.SPDF.model; import java.util.Set; +import java.util.stream.Collectors; import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Id; +import jakarta.persistence.MapKeyColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; - +import jakarta.persistence.JoinColumn; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; @Entity @Table(name = "users") public class User { @@ -25,7 +32,23 @@ public class User { private boolean enabled; @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") - private Set authorities; + private Set authorities = new HashSet<>(); + + @ElementCollection + @MapKeyColumn(name = "setting_key") + @Column(name = "setting_value") + @CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "username")) + private Map settings = new HashMap<>(); // Key-value pairs of settings. + + + + public Map getSettings() { + return settings; + } + + public void setSettings(Map settings) { + this.settings = settings; + } public String getUsername() { return username; @@ -58,5 +81,19 @@ public class User { public void setAuthorities(Set authorities) { this.authorities = authorities; } + + public void addAuthorities(Set authorities) { + this.authorities.addAll(authorities); + } + public void addAuthority(Authority authorities) { + this.authorities.add(authorities); + } + + public String getRolesAsString() { + return this.authorities.stream() + .map(Authority::getAuthority) + .collect(Collectors.joining(", ")); + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bf594bfc..e52f939e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,9 +16,9 @@ server.error.include-stacktrace=always server.error.include-exception=true server.error.include-message=always -logging.level.org.springframework.web=DEBUG -logging.level.org.springframework=DEBUG -logging.level.org.springframework.security=DEBUG +#logging.level.org.springframework.web=DEBUG +#logging.level.org.springframework=DEBUG +#logging.level.org.springframework.security=DEBUG login.enabled=true diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 182701ce..44b8cd0b 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -55,10 +55,12 @@ settings.downloadOption.2=Open in new window settings.downloadOption.3=Download file settings.zipThreshold=Zip files when the number of downloaded files exceeds +settings.accountSettings=Account Settings +settings.adminSettings=Admin - View/Add Users settings.userSettings=User Settings settings.changeUsername=New Username settings.changeUsernameButton=Change Username -settings.password=Password +settings.password=Confirmation Password settings.oldPassword=Old password settings.newPassword=New Password settings.changePasswordButton=Change Password diff --git a/src/main/resources/templates/account.html b/src/main/resources/templates/account.html new file mode 100644 index 00000000..932f98f3 --- /dev/null +++ b/src/main/resources/templates/account.html @@ -0,0 +1,194 @@ + + + + + + + +
+
+
+

+
+
+
+ + +

User Settings

+
+ + +

Welcome User!

+ + +

Change username?

+
+
+ + +
+
+ + +
+
+ +
+
+ +
+ + +

Change Password?

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ +

Sync browser settings with Account

+
+

Settings Comparison:

+ + + + + + + + + + + +
PropertyAccount SettingWeb Browser Setting
+ +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ + diff --git a/src/main/resources/templates/addUsers.html b/src/main/resources/templates/addUsers.html new file mode 100644 index 00000000..ccd7e44b --- /dev/null +++ b/src/main/resources/templates/addUsers.html @@ -0,0 +1,76 @@ + + + + + + + +
+
+
+

+
+
+
+ + +

User Settings

+
+ + +

Welcome User!

+ + + + + + + + + + + + + + + + + + +
UsernameRolesActions
+ Delete +
+ + +

Add New User

+
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+
+
+ +
+
+
+ + diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 4ae83570..74ab9383 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -339,49 +339,11 @@ -
-
User Settings
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- - - - + + + + +