From 738f0834f85d7d2da61f9d2bf0932e25c85453eb Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Fri, 28 May 2021 19:03:16 +0200 Subject: [PATCH 1/9] Fixes missing settings update and missing dependencies --- pom.xml | 7 ++++ .../de/ipvs/as/mbp/SecurityConfiguration.java | 6 --- .../RestAuthenticationEntryPoint.java | 1 - .../ipvs/as/mbp/service/mqtt/MQTTService.java | 3 +- .../mbp/service/settings/SettingsService.java | 38 ++++++++++++++++++- .../mbp/web/rest/RestSettingsController.java | 17 +++++---- 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 9e043930a..7ab2305b1 100644 --- a/pom.xml +++ b/pom.xml @@ -175,6 +175,13 @@ swagger2markup 1.3.3 + + + + cglib + cglib + 3.3.0 + diff --git a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java index 254a90fc2..aedcf6f6f 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java @@ -17,12 +17,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -/** - * Security configuration - * - * @author Imeri Amil - */ - @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) diff --git a/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java b/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java index 9cb305bcf..a609f346f 100644 --- a/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java +++ b/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java @@ -12,7 +12,6 @@ /** * Entry point in case of unauthorized access. - * @author Imeri Amil */ @Component public class RestAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { diff --git a/src/main/java/de/ipvs/as/mbp/service/mqtt/MQTTService.java b/src/main/java/de/ipvs/as/mbp/service/mqtt/MQTTService.java index a02089c1a..e7677b5bb 100644 --- a/src/main/java/de/ipvs/as/mbp/service/mqtt/MQTTService.java +++ b/src/main/java/de/ipvs/as/mbp/service/mqtt/MQTTService.java @@ -130,9 +130,8 @@ public void initialize() throws MqttException, IOException { * @param brokerLocation The broker location to use * @param brokerAddress The address of the broker to use * @throws MqttException In case of an error during execution of mqtt operations - * @throws IOException In case of an I/O issue */ - public void initialize(BrokerLocation brokerLocation, String brokerAddress) throws MqttException, IOException { + public void initialize(BrokerLocation brokerLocation, String brokerAddress) throws MqttException { //Disconnect the old mqtt client if already connected if ((mqttClient != null) && (mqttClient.isConnected())) { mqttClient.disconnectForcibly(); diff --git a/src/main/java/de/ipvs/as/mbp/service/settings/SettingsService.java b/src/main/java/de/ipvs/as/mbp/service/settings/SettingsService.java index 1166f65f7..ef0d48f98 100644 --- a/src/main/java/de/ipvs/as/mbp/service/settings/SettingsService.java +++ b/src/main/java/de/ipvs/as/mbp/service/settings/SettingsService.java @@ -1,9 +1,13 @@ package de.ipvs.as.mbp.service.settings; +import de.ipvs.as.mbp.DynamicBeanProvider; import de.ipvs.as.mbp.domain.settings.BrokerLocation; import de.ipvs.as.mbp.domain.settings.MBPInfo; import de.ipvs.as.mbp.domain.settings.Settings; import de.ipvs.as.mbp.repository.SettingsRepository; +import de.ipvs.as.mbp.service.deployment.demo.DemoDeployer; +import de.ipvs.as.mbp.service.mqtt.MQTTService; +import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.DependsOn; @@ -78,9 +82,39 @@ public Settings getSettings() { } /** - * Saves all settings that are provided as part of the settings object persistently in the MongoDB repository. + * Saves all settings that are provided as part of the settings object persistently in the MongoDB repository + * and updates affected components accordingly. * - * @param settings The settings to save + * @param settings The new settings + */ + public void updateSettings(Settings settings) throws MqttException { + //Get previous settings + Settings previousSettings = getSettings(); + + //Check whether MQTT broker settings changed + if ((!previousSettings.getBrokerLocation().equals(settings.getBrokerLocation())) || + (!previousSettings.getBrokerIPAddress().equals(settings.getBrokerIPAddress()))) { + //Broker settings changed, get MQTT service and reinitialize the connection + MQTTService mqttService = DynamicBeanProvider.get(MQTTService.class); + mqttService.initialize(settings.getBrokerLocation(), settings.getBrokerIPAddress()); + } + + //Check whether the demo mode setting changed + if (!previousSettings.isDemoMode() == settings.isDemoMode()) { + //Retrieve demo deployer component bean + DemoDeployer demoDeployer = DynamicBeanProvider.get(DemoDeployer.class); + demoDeployer.resetDeployedComponents(); + } + + //Everything worked, thus save settings to repository + saveSettings(settings); + } + + /** + * Saves all settings that are provided as part of the settings object persistently in the MongoDB repository + * without updating possibly affected components. + * + * @param settings The new settings */ public void saveSettings(Settings settings) { //Sanity check diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java index 29fd3c95d..7ed13d33a 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java @@ -3,6 +3,7 @@ import de.ipvs.as.mbp.RestConfiguration; import de.ipvs.as.mbp.domain.settings.MBPInfo; import de.ipvs.as.mbp.domain.settings.Settings; +import de.ipvs.as.mbp.error.MBPException; import de.ipvs.as.mbp.error.MissingAdminPrivilegesException; import de.ipvs.as.mbp.service.UserEntityService; import de.ipvs.as.mbp.service.settings.DefaultOperatorService; @@ -18,8 +19,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.io.IOException; - /** * REST Controller for settings related REST requests. */ @@ -87,7 +86,7 @@ public ResponseEntity addDefaultOperators() throws MissingAdminPrivilegesE @PostMapping(value = "/default-test-components") @ApiOperation(value = "Loads default components from the resource directory of the MBP and makes them available for usage in the Testing-Tool by all users.", produces = "application/hal+json") @ApiResponses({@ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 403, message = "Not authorized to perform this action"), @ApiResponse(code = 500, message = "Default operators could not be added")}) - public ResponseEntity reinstallTestingComponents() { + public ResponseEntity reinstallTestingComponents() { // Delete & reinstall all default testing components defaultTestingComponents.replaceTestDevice(); @@ -108,7 +107,7 @@ public ResponseEntity reinstallTestingComponents() { @PostMapping(value = "/test-components-redeploy") @ApiOperation(value = "Redeploy the default sensors/actuator from the resource directory of the MBP for usage in the Testing-Tool by all users.", produces = "application/hal+json") @ApiResponses({@ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 403, message = "Not authorized to perform this action"), @ApiResponse(code = 500, message = "Default operators could not be added")}) - public ResponseEntity redeployTestingComponents() { + public ResponseEntity redeployTestingComponents() { // First delete all default testing components defaultTestingComponents.redeployComponents(); @@ -118,7 +117,6 @@ public ResponseEntity redeployTestingComponents() { } - /** * Called when the client wants to retrieve the settings. * @@ -143,11 +141,16 @@ public ResponseEntity getSettings() throws MissingAdminPrivilegesExcep @PostMapping @ApiOperation(value = "Modifies the current settings of the platform", produces = "application/hal+json") @ApiResponses({@ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 403, message = "Not authorized to modify the settings")}) - public ResponseEntity saveSettings(@RequestBody Settings settings) throws MissingAdminPrivilegesException, IOException, MqttException { + public ResponseEntity saveSettings(@RequestBody Settings settings) throws MissingAdminPrivilegesException { + //Require admin permissions userEntityService.requireAdmin(); // Update settings and update MBP components if necessary - settingsService.saveSettings(settings); + try { + settingsService.updateSettings(settings); + } catch (MqttException e) { + throw new MBPException(HttpStatus.INTERNAL_SERVER_ERROR, "Could not establish connection to MQTT broker."); + } //Everything fine return ResponseEntity.ok().build(); From 65c7ceb9705ad3073cc5cff9e0b2cb83dbecd1c1 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Sat, 29 May 2021 22:06:11 +0200 Subject: [PATCH 2/9] First adjustments towards stateless user sessions --- .../java/de/ipvs/as/mbp/AuthCookieFilter.java | 56 +++++++++++++ .../de/ipvs/as/mbp/RestConfiguration.java | 3 +- .../de/ipvs/as/mbp/SecurityConfiguration.java | 77 +++++++++++++---- .../ipvs/as/mbp/SecurityWebInitializer.java | 1 - .../de/ipvs/as/mbp/UserAuthentication.java | 67 +++++++++++++++ .../de/ipvs/as/mbp/domain/user/Authority.java | 64 -------------- .../{UserAuthData.java => UserLoginData.java} | 20 ++++- .../mbp/repository/AuthorityRepository.java | 16 ---- .../RestAuthenticationEntryPoint.java | 36 -------- .../ipvs/as/mbp/security/SecurityUtils.java | 40 ++------- .../as/mbp/service/UserEntityService.java | 4 - .../de/ipvs/as/mbp/service/UserService.java | 10 +-- .../as/mbp/web/rest/RestDeviceController.java | 2 +- .../as/mbp/web/rest/RestUserController.java | 26 +++--- src/main/resources/static/js/app.js | 14 ++-- .../js/controllers/user/LoginController.js | 29 +++---- .../js/controllers/user/RegisterController.js | 10 +-- .../js/services/AuthenticationService.js | 68 --------------- .../static/js/services/HttpService.js | 27 +++--- .../static/js/services/SessionService.js | 21 ----- .../static/js/services/SettingsService.js | 7 +- .../static/js/services/UserService.js | 83 +++++++++++++++---- src/main/webapp/WEB-INF/views/index.html | 4 +- 23 files changed, 336 insertions(+), 349 deletions(-) create mode 100644 src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java create mode 100644 src/main/java/de/ipvs/as/mbp/UserAuthentication.java delete mode 100644 src/main/java/de/ipvs/as/mbp/domain/user/Authority.java rename src/main/java/de/ipvs/as/mbp/domain/user/{UserAuthData.java => UserLoginData.java} (74%) delete mode 100644 src/main/java/de/ipvs/as/mbp/repository/AuthorityRepository.java delete mode 100644 src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java delete mode 100644 src/main/resources/static/js/services/AuthenticationService.js delete mode 100644 src/main/resources/static/js/services/SessionService.js diff --git a/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java b/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java new file mode 100644 index 000000000..0842ba770 --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java @@ -0,0 +1,56 @@ +package de.ipvs.as.mbp; + +import de.ipvs.as.mbp.domain.user.User; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + + +public class AuthCookieFilter extends GenericFilterBean { + + public final static String COOKIE_NAME = "authentication"; + + public AuthCookieFilter() { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String sessionId = extractAuthenticationCookie(httpServletRequest); + + if (sessionId != null) { + User user = new User(); + user.setAdmin(true); + user.setUsername("admin"); + user.setFirstName("Test"); + user.setLastName("User"); + UserAuthentication userAuthentication = new UserAuthentication(user); + SecurityContextHolder.getContext().setAuthentication(userAuthentication); + } + + filterChain.doFilter(servletRequest, servletResponse); + } + + public static String extractAuthenticationCookie(HttpServletRequest httpServletRequest) { + String sessionId = null; + Cookie[] cookies = httpServletRequest.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(AuthCookieFilter.COOKIE_NAME)) { + sessionId = cookie.getValue(); + break; + } + } + } + return sessionId; + } +} \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/RestConfiguration.java b/src/main/java/de/ipvs/as/mbp/RestConfiguration.java index 108bed85e..11f2cccde 100644 --- a/src/main/java/de/ipvs/as/mbp/RestConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/RestConfiguration.java @@ -15,7 +15,6 @@ import de.ipvs.as.mbp.domain.rules.RuleAction; import de.ipvs.as.mbp.domain.rules.RuleTrigger; import de.ipvs.as.mbp.domain.testing.TestDetails; -import de.ipvs.as.mbp.domain.user.Authority; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; @@ -67,7 +66,7 @@ public void configureRepositoryRestConfiguration(RepositoryRestConfiguration con Device.class, Operator.class, MonitoringOperator.class, Actuator.class, Sensor.class, - User.class, Authority.class, + User.class, EntityType.class, EnvironmentModel.class, Rule.class, RuleTrigger.class, RuleAction.class, diff --git a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java index aedcf6f6f..a2862bac4 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java @@ -1,30 +1,44 @@ package de.ipvs.as.mbp; -import de.ipvs.as.mbp.security.RestAuthenticationEntryPoint; import de.ipvs.as.mbp.service.UserDetailsServiceImpl; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; +import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { - @Bean - public RestAuthenticationEntryPoint restAuthenticationEntryPoint() { - return new RestAuthenticationEntryPoint(); + private final AuthCookieFilter authCookieFilter; + + private final CustomLogoutSuccessHandler logoutSuccessHandler; + + public SecurityConfiguration() { + this.authCookieFilter = new AuthCookieFilter(); + this.logoutSuccessHandler = new CustomLogoutSuccessHandler(); } @Bean @@ -41,9 +55,7 @@ public PasswordEncoder passwordEncoder() { public void configure(AuthenticationManagerBuilder auth) { try { UserDetailsService userDetailsService = mongoUserDetails(); - auth - .userDetailsService(userDetailsService) - .passwordEncoder(passwordEncoder()); + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } catch (Exception e) { throw new BeanInitializationException("Security configuration failed", e); } @@ -67,15 +79,44 @@ public void configure(WebSecurity web) throws Exception { @Override protected void configure(HttpSecurity http) throws Exception { http - .csrf().disable() - .httpBasic().authenticationEntryPoint(restAuthenticationEntryPoint()) - .and() - .authorizeRequests() - .antMatchers(HttpMethod.POST, "/api/authenticate").permitAll() - .antMatchers("/api/**").authenticated() - .and() - .logout() - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login") - .invalidateHttpSession(true).deleteCookies("JSESSIONID"); + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .csrf(AbstractHttpConfigurer::disable) + .logout(c -> { + c.addLogoutHandler(new HeaderWriterLogoutHandler( + new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL))); + c.logoutSuccessHandler(this.logoutSuccessHandler); + c.deleteCookies(AuthCookieFilter.COOKIE_NAME); + }) + .authorizeRequests(c -> { + c.antMatchers("/api/authenticate").permitAll(); + c.antMatchers("/api/**").authenticated(); + }) + .exceptionHandling(c -> c + .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) + .addFilterAfter(this.authCookieFilter, SecurityContextPersistenceFilter.class); + + //.logout() + //.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login") + } + + private static class CustomLogoutSuccessHandler implements LogoutSuccessHandler { + + public CustomLogoutSuccessHandler() { + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + + /* + String sessionId = AuthCookieFilter.extractAuthenticationCookie(request); + if (sessionId != null) { + this.dsl.delete(APP_SESSION).where(APP_SESSION.ID.eq(sessionId)).execute(); + } + + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().flush();*/ + } + } } diff --git a/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java b/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java index 9129ae4df..956cb4962 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java @@ -4,7 +4,6 @@ /** * Security Web Initializer needed for security configuration. - * @author Imeri Amil * */ public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{ diff --git a/src/main/java/de/ipvs/as/mbp/UserAuthentication.java b/src/main/java/de/ipvs/as/mbp/UserAuthentication.java new file mode 100644 index 000000000..355327387 --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/UserAuthentication.java @@ -0,0 +1,67 @@ +package de.ipvs.as.mbp; + +import de.ipvs.as.mbp.domain.user.User; +import de.ipvs.as.mbp.domain.user.UserLoginData; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class UserAuthentication implements Authentication { + + private User user; + + public UserAuthentication(User user) { + setUser(user); + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public Object getCredentials() { + return new UserLoginData(); + } + + @Override + public Object getDetails() { + return user; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean authenticated) throws IllegalArgumentException { + throw new UnsupportedOperationException("This authentication object is always authenticated."); + } + + @Override + public String getName() { + return user.getUsername(); + } + + /** + * Sets the user object to which this authentication belongs to. + * + * @param user The user to set + */ + private void setUser(User user) { + //Sanity check + if (user == null) { + throw new IllegalArgumentException("User must not be null."); + } + + //Set user + this.user = user; + } +} diff --git a/src/main/java/de/ipvs/as/mbp/domain/user/Authority.java b/src/main/java/de/ipvs/as/mbp/domain/user/Authority.java deleted file mode 100644 index 5509dd2c6..000000000 --- a/src/main/java/de/ipvs/as/mbp/domain/user/Authority.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.ipvs.as.mbp.domain.user; - -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; -import java.io.Serializable; - -/** -* Authority entity. -* @author Imeri Amil -*/ -@Document -public class Authority implements Serializable { - - private static final long serialVersionUID = 1L; - - @NotNull - @Size(min = 0, max = 50) - @Id - private String name; - - public Authority(){ - - } - - public Authority(String name){ - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Authority authority = (Authority) o; - - return name != null ? name.equals(authority.name) : authority.name == null; - } - - @Override - public int hashCode() { - return name != null ? name.hashCode() : 0; - } - - @Override - public String toString() { - return "Authority{" + - "name='" + name + '\'' + - "}"; - } -} diff --git a/src/main/java/de/ipvs/as/mbp/domain/user/UserAuthData.java b/src/main/java/de/ipvs/as/mbp/domain/user/UserLoginData.java similarity index 74% rename from src/main/java/de/ipvs/as/mbp/domain/user/UserAuthData.java rename to src/main/java/de/ipvs/as/mbp/domain/user/UserLoginData.java index 8ad35c9e0..fa3278f1e 100644 --- a/src/main/java/de/ipvs/as/mbp/domain/user/UserAuthData.java +++ b/src/main/java/de/ipvs/as/mbp/domain/user/UserLoginData.java @@ -7,12 +7,30 @@ * Wrapper for user login data that is passed with an user authentication request. */ @ApiModel(description = "Login data for user authentication requests") -public class UserAuthData { +public class UserLoginData { @ApiModelProperty(notes = "Username of the user to authenticate", example = "MyUser", required = true) private String username; @ApiModelProperty(notes = "Password of the user to authenticate", example = "secret", required = true) private String password; + /** + * Creates a new empty user auth data object. + */ + public UserLoginData() { + + } + + /** + * Creates a new user auth data object from a given username and password. + * + * @param username The user name to use + * @param password The password to use + */ + public UserLoginData(String username, String password) { + this.username = username; + this.password = password; + } + /** * Returns the username that is part of the auth data. * diff --git a/src/main/java/de/ipvs/as/mbp/repository/AuthorityRepository.java b/src/main/java/de/ipvs/as/mbp/repository/AuthorityRepository.java deleted file mode 100644 index 3f1eaae97..000000000 --- a/src/main/java/de/ipvs/as/mbp/repository/AuthorityRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.ipvs.as.mbp.repository; - -import java.util.Optional; - -import de.ipvs.as.mbp.domain.user.Authority; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.repository.query.Param; - -/** - * Spring Data MongoDB repository for the Authority entity. - * @author Imeri Amil - */ -public interface AuthorityRepository extends MongoRepository { - - public Optional findByName(@Param("name") String name); -} diff --git a/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java b/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java deleted file mode 100644 index a609f346f..000000000 --- a/src/main/java/de/ipvs/as/mbp/security/RestAuthenticationEntryPoint.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.ipvs.as.mbp.security; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.stereotype.Component; - -/** - * Entry point in case of unauthorized access. - */ -@Component -public class RestAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { - - @Override - public void commence( - final HttpServletRequest request, - final HttpServletResponse response, - final AuthenticationException authException) throws IOException { - - response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName()); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - PrintWriter writer = response.getWriter(); - writer.println("HTTP Status 401 - " + authException.getMessage()); - } - - @Override - public void afterPropertiesSet() { - setRealmName("MBP"); - super.afterPropertiesSet(); - } -} \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java b/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java index 745e16a75..ea4fbe0da 100644 --- a/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java +++ b/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java @@ -1,14 +1,13 @@ package de.ipvs.as.mbp.security; -import de.ipvs.as.mbp.constants.Constants; +import de.ipvs.as.mbp.UserAuthentication; +import de.ipvs.as.mbp.domain.user.User; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; /** * Utility class for Spring Security. - * @author Imeri Amil */ public final class SecurityUtils { @@ -17,49 +16,28 @@ private SecurityUtils() { /** * Get the username of the current user. + * * @return the username of the current user */ public static String getCurrentUserUsername() { SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication authentication = securityContext.getAuthentication(); - String username = null; - if (authentication != null) { - if (authentication.getPrincipal() instanceof UserDetails) { - UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); - username = springSecurityUser.getUsername(); - } else if (authentication.getPrincipal() instanceof String) { - username = (String) authentication.getPrincipal(); - } + if (authentication == null) { + return null; } - return username; + + return ((User) authentication.getDetails()).getUsername(); } /** * Check if a user is authenticated. + * * @return true if the user is authenticated, false otherwise */ public static boolean isAuthenticated() { SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication authentication = securityContext.getAuthentication(); - if (authentication != null) { - return authentication.getAuthorities().stream() - .noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(Constants.ANONYMOUS)); - } - return false; + return (authentication != null); } - /** - * If the current user has a specific authority (security role). - * @param authority the authority to check - * @return true if the current user has the authority, false otherwise - */ - public static boolean isCurrentUserInRole(String authority) { - SecurityContext securityContext = SecurityContextHolder.getContext(); - Authentication authentication = securityContext.getAuthentication(); - if (authentication != null) { - return authentication.getAuthorities().stream() - .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)); - } - return false; - } } diff --git a/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java b/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java index b6661fa25..15b50583b 100644 --- a/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java +++ b/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java @@ -285,10 +285,6 @@ public void requireAdmin() throws MissingAdminPrivilegesException { requireAdmin(userService.getLoggedInUser()); } - public void requireAdmin(String userId) throws MissingAdminPrivilegesException { - requireAdmin(userService.getForId(userId)); - } - public void requireAdmin(User user) throws MissingAdminPrivilegesException { if (!user.isAdmin()) { throw new MissingAdminPrivilegesException(); diff --git a/src/main/java/de/ipvs/as/mbp/service/UserService.java b/src/main/java/de/ipvs/as/mbp/service/UserService.java index 682f2b1d2..ca860eaec 100644 --- a/src/main/java/de/ipvs/as/mbp/service/UserService.java +++ b/src/main/java/de/ipvs/as/mbp/service/UserService.java @@ -34,15 +34,13 @@ public User getLoggedInUser() { } public User getForId(String id) { - return userRepository.findById(id).orElseThrow( - () -> new MBPException(HttpStatus.NOT_FOUND, - "User with id '" + id + "' does not exist!")); + return userRepository.findById(id).orElseThrow(() -> new MBPException(HttpStatus.NOT_FOUND, "User with ID '" + + id + "' does not exist!")); } public User getForUsername(String username) { - return userRepository.findByUsername(username).orElseThrow( - () -> new MBPException(HttpStatus.NOT_FOUND, - "User with username '" + username + "' does not exist!")); + return userRepository.findByUsername(username).orElseThrow(() -> new MBPException(HttpStatus.NOT_FOUND, + "User with username '" + username + "' does not exist!")); } public User create(User user) { diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java index 6a2b4617e..02dd71452 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java @@ -87,7 +87,7 @@ public ResponseEntity> one( @RequestHeader("X-MBP-Access-Request") String accessRequestHeader, @PathVariable("deviceId") String deviceId, @ApiParam(value = "Page parameters", required = true) Pageable pageable) throws EntityNotFoundException, MissingPermissionException { - // Parse the access-request information + // Parse the access request information ACAccessRequest accessRequest = ACAccessRequest.valueOf(accessRequestHeader); // Retrieve the corresponding device (includes access-control) diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java index c8bc6e0cb..0bf365ee3 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java @@ -1,9 +1,10 @@ package de.ipvs.as.mbp.web.rest; +import de.ipvs.as.mbp.AuthCookieFilter; import de.ipvs.as.mbp.RestConfiguration; import de.ipvs.as.mbp.constants.Constants; import de.ipvs.as.mbp.domain.user.User; -import de.ipvs.as.mbp.domain.user.UserAuthData; +import de.ipvs.as.mbp.domain.user.UserLoginData; import de.ipvs.as.mbp.error.*; import de.ipvs.as.mbp.repository.UserRepository; import de.ipvs.as.mbp.repository.projection.UserExcerpt; @@ -14,9 +15,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.PagedModel; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -89,14 +88,14 @@ public ResponseEntity> searchByUsername(@RequestParam("query") return query.isEmpty() ? ResponseEntity.ok(new ArrayList<>()) : ResponseEntity.ok(userRepository.findByUsernameContains(query.trim())); } - @PostMapping(value = "/authenticate") - @ApiOperation(value = "Authenticates a user", produces = "application/hal+json") + @PostMapping(value = "/login") + @ApiOperation(value = "Performs login for a user", produces = "application/hal+json") @ApiResponses({@ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 403, message = "Invalid password!"), @ApiResponse(code = 404, message = "User or requesting user not found!")}) - public ResponseEntity authenticate(@RequestBody @ApiParam(value = "Authentication data", required = true) UserAuthData authData) throws InvalidPasswordException, UserNotLoginableException { + public ResponseEntity login(@RequestBody @ApiParam(value = "Login data", required = true) UserLoginData loginData) throws InvalidPasswordException, UserNotLoginableException { // Retrieve user from database - User user = userService.getForUsername(authData.getUsername().toLowerCase(Locale.ENGLISH)); + User user = userService.getForUsername(loginData.getUsername().toLowerCase(Locale.ENGLISH)); //Check if login into user is possible if (!user.isLoginable()) { @@ -104,8 +103,15 @@ public ResponseEntity authenticate(@RequestBody @ApiParam(value = "Authent } // Check password - if (userService.checkPassword(user.getId(), authData.getPassword())) { - return ResponseEntity.ok(user); + if (userService.checkPassword(user.getId(), loginData.getPassword())) { + + ResponseCookie cookie = ResponseCookie + .from(AuthCookieFilter.COOKIE_NAME, "asdfasdfsession234") + .maxAge(999999).sameSite("Strict") + .path("/").httpOnly(true).secure(true).build(); + + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) + .body(user); } else { throw new InvalidPasswordException(); } diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 08175af9d..f74192451 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -589,18 +589,15 @@ app.config(['$provide', '$routeProvider', '$locationProvider', '$resourceProvide } ]); -app.run(['$rootScope', '$timeout', 'SessionService', '$location', '$cookieStore', - function ($rootScope, $timeout, SessionService, $location, $cookieStore) { +app.run(['$rootScope', '$cookieStore', '$timeout', '$location', + function ($rootScope, $cookieStore, $timeout, $location) { //Keep user logged in after page refresh $rootScope.globals = $cookieStore.get('globals') || {}; - /* - if ($rootScope.globals.currentUser) { - $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; - }*/ - $rootScope.$on('$locationChangeStart', function (event, next, current) { - // redirect to login page if not logged in and trying to access a restricted page + //Register listener for location change + $rootScope.$on('$locationChangeStart', function () { + // Redirect user to login page if not logged in let restrictedPage = $.inArray($location.path(), ['/login', '/register']) === -1; let loggedIn = $rootScope.globals.currentUser; if (restrictedPage && !loggedIn) { @@ -610,7 +607,6 @@ app.run(['$rootScope', '$timeout', 'SessionService', '$location', '$cookieStore' $rootScope.$on('$viewContentLoaded', function () { $timeout(function () { - $rootScope.loggedIn = $rootScope.globals.currentUser; if ($rootScope.loggedIn) { diff --git a/src/main/resources/static/js/controllers/user/LoginController.js b/src/main/resources/static/js/controllers/user/LoginController.js index c42533d7e..75faf29f5 100644 --- a/src/main/resources/static/js/controllers/user/LoginController.js +++ b/src/main/resources/static/js/controllers/user/LoginController.js @@ -1,36 +1,29 @@ -app.controller('LoginController', ['$scope', '$location', '$rootScope', 'AuthenticationService', 'NotificationService', - function ($scope, $location, $rootScope, AuthenticationService, NotificationService) { +app.controller('LoginController', ['$scope', '$location', 'UserService', + function ($scope, $location, UserService) { let vm = this; - vm.dataLoading = false; - /** * Initializing function, sets up basic things. */ (function initController() { //Reset login status - AuthenticationService.ClearCredentials(); - AuthenticationService.Logout(); + UserService.logoutUser(); + + //Hide loader + vm.dataLoading = false; })(); /** * [Public] - * Performs user login with the form data from the template. + * Performs a server request in order to login a user with the entered form data. */ function login() { + //Show loader vm.dataLoading = true; - AuthenticationService.Login(vm.username, vm.password).then(function (userData) { - //Sanitize user data - userData = userData || []; - - //Enable authorization locally - AuthenticationService.SetCredentials(vm.username, vm.password, userData); - - //Redirect - $location.path('/'); - }, function (response) { - }).then(function () { + //Perform login + UserService.loginUser(vm.username, vm.password).then(function (userData) { + //Hide loader vm.dataLoading = false; $scope.$apply(); }); diff --git a/src/main/resources/static/js/controllers/user/RegisterController.js b/src/main/resources/static/js/controllers/user/RegisterController.js index c5f465a90..3f43f43cd 100644 --- a/src/main/resources/static/js/controllers/user/RegisterController.js +++ b/src/main/resources/static/js/controllers/user/RegisterController.js @@ -1,13 +1,13 @@ /** * Controller for the rules list page. */ -app.controller('RegisterController', ['$scope', 'UserService', '$location', 'NotificationService', - function ($scope, UserService, $location, NotificationService) { +app.controller('RegisterController', ['$scope', '$location', 'UserService', 'NotificationService', + function ($scope, $location, UserService, NotificationService) { let vm = this; - function register() { + function registerUser() { vm.dataLoading = true; - UserService.Create(vm.user).then(function (response) { + UserService.createUser(vm.user).then(function (response) { NotificationService.showSuccess("Registration was successful!"); //Redirect @@ -22,7 +22,7 @@ app.controller('RegisterController', ['$scope', 'UserService', '$location', 'Not //Expose functions angular.extend(vm, { - register: register + register: registerUser }); }] ); \ No newline at end of file diff --git a/src/main/resources/static/js/services/AuthenticationService.js b/src/main/resources/static/js/services/AuthenticationService.js deleted file mode 100644 index 4db936429..000000000 --- a/src/main/resources/static/js/services/AuthenticationService.js +++ /dev/null @@ -1,68 +0,0 @@ -app.factory('AuthenticationService', ['HttpService', '$cookieStore', '$rootScope', '$timeout', 'UserService', - function (HttpService, $cookieStore, $rootScope, $timeout, UserService) { - - function encodeBase64(rawString) { - let wordArray = CryptoJS.enc.Utf8.parse(rawString); - return CryptoJS.enc.Base64.stringify(wordArray); - } - - function login(username, password, callback) { - let user = { - username: username, - password: password - }; - - return UserService.Authenticate(user); - } - - function logout() { - UserService.Logout().then(function (response) { - }, function (response) { - console.log("Log out error!"); - }); - } - - /* - * Set the authorization header and save the data in a cookie - */ - function setCredentials(username, password, userData) { - //Encrypt username and password via Base64 to auth data - let authData = encodeBase64(username + ':' + password); - - //Sanitize user data - userData = userData || {}; - - $rootScope.globals = { - currentUser: { - username: username, - userData: userData, - authdata: authData - } - }; - - // store user details in globals cookie that keeps user logged in for 1 week (or until they logout) - let cookieExp = new Date(); - cookieExp.setDate(cookieExp.getDate() + 7); - $cookieStore.put('globals', $rootScope.globals, { - expires: cookieExp - }); - } - - /* - * Remove the header and delete the cookie - */ - function clearCredentials() { - $rootScope.globals = {}; - $cookieStore.remove('globals'); - } - - //Expose - return { - Login: login, - Logout: logout, - SetCredentials: setCredentials, - ClearCredentials: clearCredentials - }; - } - ] -); diff --git a/src/main/resources/static/js/services/HttpService.js b/src/main/resources/static/js/services/HttpService.js index f6a0ad55f..ac7302630 100644 --- a/src/main/resources/static/js/services/HttpService.js +++ b/src/main/resources/static/js/services/HttpService.js @@ -35,17 +35,10 @@ app.factory('HttpService', ['$rootScope', '$interval', 'ENDPOINT_URI', 'Notifica */ function generateHeader() { //Create header object - let headers = { + return { 'Content-Type': 'application/json;charset=UTF8', 'X-MBP-Access-Request': getUserAttributes() - } - - //Check if authorization can be added to the header - if ($rootScope.globals.currentUser) { - headers['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; - } - - return headers; + }; } /** @@ -54,8 +47,10 @@ app.factory('HttpService', ['$rootScope', '$interval', 'ENDPOINT_URI', 'Notifica * @returns The created XMLHttpRequest */ function createXHR() { + //Create XHR let xhr = new window.XMLHttpRequest(); + //Register listeners for start and end xhr.addEventListener("loadstart", onRequestStart, false); xhr.addEventListener("loadend", onRequestFinished, false); @@ -370,13 +365,13 @@ app.factory('HttpService', ['$rootScope', '$interval', 'ENDPOINT_URI', 'Notifica angular.forEach(data._embedded, - function(value, key) { - if(key === "sensors" || key === "devices" || key === "actuators"){ - value.some(function (component) { - if (component.name.includes("TESTING_")) { - counter = counter -1; - } - }); + function (value, key) { + if (key === "sensors" || key === "devices" || key === "actuators") { + value.some(function (component) { + if (component.name.includes("TESTING_")) { + counter = counter - 1; + } + }); } }); diff --git a/src/main/resources/static/js/services/SessionService.js b/src/main/resources/static/js/services/SessionService.js deleted file mode 100644 index a1fba1b3e..000000000 --- a/src/main/resources/static/js/services/SessionService.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global app */ - -'use strict'; - -app.factory('SessionService', ['$cookieStore', function ($cookieStore) { - var cookieName = 'expert-mode'; - - return { - isExpert: function() { - return $cookieStore.get(cookieName) === 'true'; - }, - - goExpert: function() { - $cookieStore.put(cookieName, 'true'); - }, - - leaveExpert: function() { - $cookieStore.remove(cookieName); - } - }; -}]); \ No newline at end of file diff --git a/src/main/resources/static/js/services/SettingsService.js b/src/main/resources/static/js/services/SettingsService.js index 0e8d3ab34..94c6b7fdc 100644 --- a/src/main/resources/static/js/services/SettingsService.js +++ b/src/main/resources/static/js/services/SettingsService.js @@ -3,8 +3,8 @@ /** * Provides services for managing the application settings. */ -app.factory('SettingsService', ['HttpService', 'ENDPOINT_URI', - function (HttpService, ENDPOINT_URI) { +app.factory('SettingsService', ['ENDPOINT_URI', 'HttpService', + function (ENDPOINT_URI, HttpService) { //URLs for server requests const URL_SETTINGS = ENDPOINT_URI + '/settings'; const URL_DEFAULT_OPERATORS = ENDPOINT_URI + '/settings/default-operators'; @@ -23,7 +23,6 @@ app.factory('SettingsService', ['HttpService', 'ENDPOINT_URI', } - /** * [Public] * Performs a server request in order to reinstall default components for the Testing-Tool. @@ -38,7 +37,7 @@ app.factory('SettingsService', ['HttpService', 'ENDPOINT_URI', * Performs a server request in order to redeploy default components for the Testing-Tool. * @returns {*} */ - function redeployTestingComponents(){ + function redeployTestingComponents() { return HttpService.postRequest(URL_REDEPLOY_TEST_COMPONENTS) } diff --git a/src/main/resources/static/js/services/UserService.js b/src/main/resources/static/js/services/UserService.js index a55fc7ad2..dc7cceb98 100644 --- a/src/main/resources/static/js/services/UserService.js +++ b/src/main/resources/static/js/services/UserService.js @@ -1,22 +1,37 @@ /** * Provides services for managing users. */ -app.factory('UserService', ['$rootScope', 'HttpService', 'ENDPOINT_URI', 'BASE_URI', - function ($rootScope, HttpService, ENDPOINT_URI, BASE_URI) { +app.factory('UserService', ['$rootScope', '$location', '$cookieStore', 'HttpService', 'ENDPOINT_URI', + function ($rootScope, $location, $cookieStore, HttpService, ENDPOINT_URI) { + const URL_LOGIN_SUFFIX = '/users/login'; + const URL_LOGOUT_SUFFIX = 'users/logout'; const URL_PROMOTE_SUFFIX = '/promote'; const URL_DEGRADE_SUFFIX = '/degrade'; const URL_CHANGE_PASSWORD_SUFFIX = '/change_password'; - function authenticate(user) { - return HttpService.postRequest(ENDPOINT_URI + '/users/authenticate', user); + function loginUser(username, password) { + return HttpService.postRequest(ENDPOINT_URI + URL_LOGIN_SUFFIX, { + "username": username, + "password": password + }).then(function (userData) { + //Sanitize user data + userData = userData || []; + + //Enable authorization locally + setUserData(username, userData); + + //Redirect + $location.path('/'); + }); } - function logout() { - return HttpService.getRequest(BASE_URI + 'logout'); + function logoutUser() { + //Clear user data + clearUserData(); } - function getAll() { + function getAllUsers() { return HttpService.getRequest(ENDPOINT_URI + '/users'); } @@ -45,18 +60,56 @@ app.factory('UserService', ['$rootScope', 'HttpService', 'ENDPOINT_URI', 'BASE_U } function changeUserPassword(userId, newPassword) { - return HttpService.postRequest(ENDPOINT_URI + '/users/' + userId + URL_CHANGE_PASSWORD_SUFFIX, {'password': newPassword}); + return HttpService.postRequest(ENDPOINT_URI + '/users/' + userId + URL_CHANGE_PASSWORD_SUFFIX, + {'password': newPassword}); + } + + /** + * [Private] + * Takes data about the current user and stores it in the root scope for easy access. In addition, + * a cookie is created to remember to user data across switching pages. + * + * @param username The username of the current user + * @param userData Additional data about the current user + */ + function setUserData(username, userData) { + //Sanitize user data + userData = userData || {}; + + //Store user data in root scope for easy access + $rootScope.globals = { + currentUser: { + username: username, + userData: userData + } + }; + + // Store user details in globals cookie that keeps user logged in for one week + let cookieExp = new Date(); + cookieExp.setDate(cookieExp.getDate() + 7); + $cookieStore.put('globals', $rootScope.globals, { + expires: cookieExp + }); + } + + /** + * [Private] + * Clears the data about the current user from the root scope and also removes to corresponding cookie. + */ + function clearUserData() { + $rootScope.globals = {}; + $cookieStore.remove('globals'); } //Expose return { - Authenticate: authenticate, - Logout: logout, - GetAll: getAll, - GetByUsername: getByUsername, - Create: createUser, - Update: updateUser, - Delete: deleteUser, + loginUser: loginUser, + logoutUser: logoutUser, + getAllUsers: getAllUsers, + getByUsername: getByUsername, + createUser: createUser, + updateUser: updateUser, + deleteUser: deleteUser, promoteUser: promoteUser, degradeUser: degradeUser, changeUserPassword: changeUserPassword diff --git a/src/main/webapp/WEB-INF/views/index.html b/src/main/webapp/WEB-INF/views/index.html index 31041d8fb..5443075ba 100644 --- a/src/main/webapp/WEB-INF/views/index.html +++ b/src/main/webapp/WEB-INF/views/index.html @@ -101,7 +101,7 @@ - - - From 52b4d3baf2f52c074ef934f8ef9dad3e3da20798 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Sun, 30 May 2021 03:44:00 +0200 Subject: [PATCH 3/9] Further adjustments --- .../java/de/ipvs/as/mbp/AuthCookieFilter.java | 21 ++++- .../de/ipvs/as/mbp/SecurityConfiguration.java | 55 ++++--------- .../ipvs/as/mbp/domain/user/UserSession.java | 77 +++++++++++++++++++ .../mbp/repository/UserSessionRepository.java | 47 +++++++++++ .../ipvs/as/mbp/security/SecurityUtils.java | 19 +++-- .../env_model/EnvironmentModelService.java | 2 +- .../mbp/service/logs/ExceptionLogService.java | 2 +- .../{ => user}/UserDetailsServiceImpl.java | 2 +- .../service/{ => user}/UserEntityService.java | 2 +- .../mbp/service/{ => user}/UserService.java | 2 +- .../mbp/service/user/UserSessionService.java | 49 ++++++++++++ .../web/rest/RestACConditionController.java | 2 +- .../mbp/web/rest/RestACEffectController.java | 2 +- .../mbp/web/rest/RestACPolicyController.java | 2 +- .../mbp/web/rest/RestActuatorController.java | 2 +- .../web/rest/RestActuatorTypeController.java | 2 +- .../rest/RestComponentFilterController.java | 2 +- .../rest/RestComponentStateController.java | 2 +- .../web/rest/RestDeploymentController.java | 2 +- .../as/mbp/web/rest/RestDeviceController.java | 2 +- .../web/rest/RestDeviceStateController.java | 2 +- .../web/rest/RestDeviceTypeController.java | 2 +- .../web/rest/RestDocumentationController.java | 2 +- .../mbp/web/rest/RestEnvModelController.java | 2 +- .../mbp/web/rest/RestKeyPairController.java | 4 +- .../as/mbp/web/rest/RestLogController.java | 2 +- .../web/rest/RestMonitoringController.java | 4 +- .../RestMonitoringOperatorController.java | 2 +- .../mbp/web/rest/RestOperatorController.java | 2 +- .../web/rest/RestRuleActionController.java | 2 +- .../as/mbp/web/rest/RestRuleController.java | 2 +- .../web/rest/RestRuleTriggerController.java | 2 +- .../as/mbp/web/rest/RestSensorController.java | 2 +- .../web/rest/RestSensorTypeController.java | 2 +- .../mbp/web/rest/RestSettingsController.java | 2 +- .../mbp/web/rest/RestTestingController.java | 3 +- .../as/mbp/web/rest/RestUserController.java | 24 +++--- .../mbp/web/rest/RestValueLogController.java | 2 +- .../web/rest/RestValueLogStatsController.java | 2 +- .../mbp/web/rest/helper/MonitoringHelper.java | 2 +- .../static/js/services/UserService.js | 1 - src/main/webapp/WEB-INF/views/index.html | 2 +- 42 files changed, 266 insertions(+), 100 deletions(-) create mode 100644 src/main/java/de/ipvs/as/mbp/domain/user/UserSession.java create mode 100644 src/main/java/de/ipvs/as/mbp/repository/UserSessionRepository.java rename src/main/java/de/ipvs/as/mbp/service/{ => user}/UserDetailsServiceImpl.java (97%) rename src/main/java/de/ipvs/as/mbp/service/{ => user}/UserEntityService.java (99%) rename src/main/java/de/ipvs/as/mbp/service/{ => user}/UserService.java (99%) create mode 100644 src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java diff --git a/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java b/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java index 0842ba770..859947ba8 100644 --- a/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java +++ b/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java @@ -1,7 +1,10 @@ package de.ipvs.as.mbp; import de.ipvs.as.mbp.domain.user.User; +import de.ipvs.as.mbp.repository.UserSessionRepository; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; @@ -10,14 +13,18 @@ import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; -public class AuthCookieFilter extends GenericFilterBean { +public class AuthCookieFilter extends GenericFilterBean implements LogoutSuccessHandler { public final static String COOKIE_NAME = "authentication"; - public AuthCookieFilter() { + private UserSessionRepository userSessionRepository; + + public AuthCookieFilter(UserSessionRepository userSessionRepository) { + this.userSessionRepository = userSessionRepository; } @Override @@ -53,4 +60,14 @@ public static String extractAuthenticationCookie(HttpServletRequest httpServletR } return sessionId; } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + System.out.println("asdf"); + + String sessionId = extractAuthenticationCookie(request); + //TODO delete session + + response.sendRedirect(request.getContextPath() + SecurityConfiguration.URL_LOGIN); + } } \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java index a2862bac4..f6e04cf30 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java @@ -1,7 +1,9 @@ package de.ipvs.as.mbp; -import de.ipvs.as.mbp.service.UserDetailsServiceImpl; +import de.ipvs.as.mbp.repository.UserSessionRepository; +import de.ipvs.as.mbp.service.user.UserDetailsServiceImpl; import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -14,31 +16,27 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.context.SecurityContextPersistenceFilter; -import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { - private final AuthCookieFilter authCookieFilter; + //URLs to important pages + public static final String URL_LOGIN = "/login"; + public static final String URL_LOGOUT = "/logout"; - private final CustomLogoutSuccessHandler logoutSuccessHandler; + private final AuthCookieFilter authCookieFilter; - public SecurityConfiguration() { - this.authCookieFilter = new AuthCookieFilter(); - this.logoutSuccessHandler = new CustomLogoutSuccessHandler(); + @Autowired + public SecurityConfiguration(UserSessionRepository userSessionRepository) { + this.authCookieFilter = new AuthCookieFilter(userSessionRepository); } @Bean @@ -67,7 +65,7 @@ public void configure(WebSecurity web) throws Exception { .antMatchers(HttpMethod.OPTIONS, "/**") .antMatchers("/resources/**") .antMatchers("/webapp/**") - .antMatchers("/login", "/templates/register") + .antMatchers(URL_LOGIN, "/templates/register") .antMatchers(HttpMethod.GET, RestConfiguration.BASE_PATH + "/settings/mbpinfo") .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/users/authenticate") .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/users") @@ -82,10 +80,9 @@ protected void configure(HttpSecurity http) throws Exception { .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(AbstractHttpConfigurer::disable) .logout(c -> { - c.addLogoutHandler(new HeaderWriterLogoutHandler( - new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL))); - c.logoutSuccessHandler(this.logoutSuccessHandler); + c.logoutRequestMatcher(new AntPathRequestMatcher(URL_LOGOUT)); c.deleteCookies(AuthCookieFilter.COOKIE_NAME); + c.logoutSuccessHandler(authCookieFilter); }) .authorizeRequests(c -> { c.antMatchers("/api/authenticate").permitAll(); @@ -94,29 +91,5 @@ protected void configure(HttpSecurity http) throws Exception { .exceptionHandling(c -> c .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) .addFilterAfter(this.authCookieFilter, SecurityContextPersistenceFilter.class); - - //.logout() - //.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login") - } - - private static class CustomLogoutSuccessHandler implements LogoutSuccessHandler { - - public CustomLogoutSuccessHandler() { - } - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - - /* - String sessionId = AuthCookieFilter.extractAuthenticationCookie(request); - if (sessionId != null) { - this.dsl.delete(APP_SESSION).where(APP_SESSION.ID.eq(sessionId)).execute(); - } - - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().flush();*/ - } - } } diff --git a/src/main/java/de/ipvs/as/mbp/domain/user/UserSession.java b/src/main/java/de/ipvs/as/mbp/domain/user/UserSession.java new file mode 100644 index 000000000..24b79c1ae --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/domain/user/UserSession.java @@ -0,0 +1,77 @@ +package de.ipvs.as.mbp.domain.user; + +import io.swagger.annotations.ApiModelProperty; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import javax.persistence.GeneratedValue; +import java.time.Instant; + +/** + * Objects of this class represent sessions of authenticated users. + */ +@Document +public class UserSession { + @Id + @GeneratedValue + @ApiModelProperty(notes = "Session ID", example = "5c8f7ad66f9e3c1bacb0fa99", accessMode = ApiModelProperty.AccessMode.READ_ONLY, readOnly = true) + private String sessionId; + + @ApiModelProperty(notes = "User ID", example = "5c8f7ad66f9e3c1bacb0fa99", accessMode = ApiModelProperty.AccessMode.READ_ONLY, readOnly = true) + private String userId; + + @ApiModelProperty(notes = "Creation timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY, readOnly = true) + private Instant created; + + /** + * Creates a new empty session object. + */ + public UserSession() { + + } + + /** + * Creates a new session object for a given user. + * + * @param user The user for which the session object is supposed to be created + */ + public UserSession(User user) { + //Sanity check + if (user == null) { + throw new IllegalArgumentException("User must not be null."); + } + + //Extract user ID + this.userId = user.getId(); + + //Set creation timestamp + this.created = Instant.now(); + } + + /** + * Returns the session ID. + * + * @return The session ID + */ + public String getSessionId() { + return sessionId; + } + + /** + * Returns the ID of the user to which the session belongs. + * + * @return The user ID + */ + public String getUserId() { + return userId; + } + + /** + * Returns the timestamp of when the session was created. + * + * @return The creation timestamp + */ + public Instant getCreated() { + return created; + } +} diff --git a/src/main/java/de/ipvs/as/mbp/repository/UserSessionRepository.java b/src/main/java/de/ipvs/as/mbp/repository/UserSessionRepository.java new file mode 100644 index 000000000..731542896 --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/repository/UserSessionRepository.java @@ -0,0 +1,47 @@ +package de.ipvs.as.mbp.repository; + +import de.ipvs.as.mbp.domain.user.UserSession; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * MongoDB repository for user sessions. + */ +@Repository +public interface UserSessionRepository extends MongoRepository { + + /** + * Returns whether an user session with a given session ID exists in the repository. + * + * @param sessionId The session ID to check + * @return True, if an user session with the given ID exists; false otherwise + */ + boolean existsBySessionId(@Param("sessionId") String sessionId); + + /** + * Returns whether an user session with a given user ID exists in the repository. + * + * @param userId The user ID to check + * @return True, if an user session with the given ID exists; false otherwise + */ + boolean existsByUserId(@Param("userId") String userId); + + /** + * Returns the first user session from the repository which has a given session ID. + * + * @param sessionId The session ID to search for + * @return Optional containing the first matching user session (if existing) + */ + Optional findFirstBySessionId(@Param("sessionId") String sessionId); + + /** + * Returns the first user session from the repository which has a given user ID. + * + * @param userId The user ID to search for + * @return Optional containing the first matching user session (if existing) + */ + Optional findFirstByUserId(@Param("userId") String userId); +} diff --git a/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java b/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java index ea4fbe0da..681abe561 100644 --- a/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java +++ b/src/main/java/de/ipvs/as/mbp/security/SecurityUtils.java @@ -1,32 +1,34 @@ package de.ipvs.as.mbp.security; -import de.ipvs.as.mbp.UserAuthentication; import de.ipvs.as.mbp.domain.user.User; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** - * Utility class for Spring Security. + * Collection of utility functions for Spring Security. */ public final class SecurityUtils { - - private SecurityUtils() { - } - /** * Get the username of the current user. * * @return the username of the current user */ public static String getCurrentUserUsername() { + //Get security context SecurityContext securityContext = SecurityContextHolder.getContext(); + + //Get current authentication from security context Authentication authentication = securityContext.getAuthentication(); if (authentication == null) { return null; } - return ((User) authentication.getDetails()).getUsername(); + //Get user from authentication + User user = (User) authentication.getDetails(); + + //Extract username + return user.getUsername(); } /** @@ -35,7 +37,10 @@ public static String getCurrentUserUsername() { * @return true if the user is authenticated, false otherwise */ public static boolean isAuthenticated() { + //Get security context SecurityContext securityContext = SecurityContextHolder.getContext(); + + //Get current authentication from security context Authentication authentication = securityContext.getAuthentication(); return (authentication != null); } diff --git a/src/main/java/de/ipvs/as/mbp/service/env_model/EnvironmentModelService.java b/src/main/java/de/ipvs/as/mbp/service/env_model/EnvironmentModelService.java index 08e75f18b..82345d414 100644 --- a/src/main/java/de/ipvs/as/mbp/service/env_model/EnvironmentModelService.java +++ b/src/main/java/de/ipvs/as/mbp/service/env_model/EnvironmentModelService.java @@ -14,7 +14,7 @@ import de.ipvs.as.mbp.error.EnvironmentModelParseException; import de.ipvs.as.mbp.error.MBPException; import de.ipvs.as.mbp.repository.*; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.service.deployment.ComponentState; import de.ipvs.as.mbp.service.deployment.DeployerDispatcher; import de.ipvs.as.mbp.service.deployment.IDeployer; diff --git a/src/main/java/de/ipvs/as/mbp/service/logs/ExceptionLogService.java b/src/main/java/de/ipvs/as/mbp/service/logs/ExceptionLogService.java index ac07a1b0e..bbe3653b9 100644 --- a/src/main/java/de/ipvs/as/mbp/service/logs/ExceptionLogService.java +++ b/src/main/java/de/ipvs/as/mbp/service/logs/ExceptionLogService.java @@ -2,7 +2,7 @@ import de.ipvs.as.mbp.domain.logs.ExceptionLog; import de.ipvs.as.mbp.repository.ExceptionLogRepository; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/ipvs/as/mbp/service/UserDetailsServiceImpl.java b/src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java similarity index 97% rename from src/main/java/de/ipvs/as/mbp/service/UserDetailsServiceImpl.java rename to src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java index 1a5039d89..cf8590bd0 100644 --- a/src/main/java/de/ipvs/as/mbp/service/UserDetailsServiceImpl.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java @@ -1,4 +1,4 @@ -package de.ipvs.as.mbp.service; +package de.ipvs.as.mbp.service.user; import java.util.Collections; import java.util.Locale; diff --git a/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserEntityService.java similarity index 99% rename from src/main/java/de/ipvs/as/mbp/service/UserEntityService.java rename to src/main/java/de/ipvs/as/mbp/service/user/UserEntityService.java index 15b50583b..e3a78e7a7 100644 --- a/src/main/java/de/ipvs/as/mbp/service/UserEntityService.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserEntityService.java @@ -1,4 +1,4 @@ -package de.ipvs.as.mbp.service; +package de.ipvs.as.mbp.service.user; import de.ipvs.as.mbp.DynamicBeanProvider; import de.ipvs.as.mbp.domain.access_control.*; diff --git a/src/main/java/de/ipvs/as/mbp/service/UserService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserService.java similarity index 99% rename from src/main/java/de/ipvs/as/mbp/service/UserService.java rename to src/main/java/de/ipvs/as/mbp/service/user/UserService.java index ca860eaec..128a9b7f8 100644 --- a/src/main/java/de/ipvs/as/mbp/service/UserService.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserService.java @@ -1,4 +1,4 @@ -package de.ipvs.as.mbp.service; +package de.ipvs.as.mbp.service.user; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.error.MBPException; diff --git a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java new file mode 100644 index 000000000..dc00bf9e9 --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java @@ -0,0 +1,49 @@ +package de.ipvs.as.mbp.service.user; + +import de.ipvs.as.mbp.AuthCookieFilter; +import de.ipvs.as.mbp.domain.user.User; +import de.ipvs.as.mbp.domain.user.UserSession; +import de.ipvs.as.mbp.repository.UserRepository; +import de.ipvs.as.mbp.repository.UserSessionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +/** + * Service class for managing user sessions. + */ +@Service +public class UserSessionService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserSessionRepository userSessionRepository; + + public ResponseCookie createSessionCookie(User user) { + //Create new session for given user + UserSession userSession = createSession(user); + + //Create corresponding cookie from session + return ResponseCookie + .from(AuthCookieFilter.COOKIE_NAME, userSession.getSessionId()) + .maxAge(Duration.ofDays(30)).sameSite("strict").httpOnly(true).secure(true) + .path("/").build(); + } + + private UserSession createSession(User user) { + //Sanity check + if (user == null) { + throw new IllegalArgumentException("User must not be null."); + } + + //Create new session + UserSession userSession = new UserSession(user); + + //Store session in repository + return userSessionRepository.insert(userSession); + } +} \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestACConditionController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestACConditionController.java index ffbcbe3ef..d762ddf45 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestACConditionController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestACConditionController.java @@ -14,7 +14,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.EntityStillInUseException; import de.ipvs.as.mbp.error.MissingOwnerPrivilegesException; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.util.C; import de.ipvs.as.mbp.util.Pages; import de.ipvs.as.mbp.domain.access_control.ACAbstractCondition; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestACEffectController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestACEffectController.java index d1a15dc89..08337ad51 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestACEffectController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestACEffectController.java @@ -13,7 +13,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.EntityStillInUseException; import de.ipvs.as.mbp.error.MissingOwnerPrivilegesException; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.util.C; import de.ipvs.as.mbp.util.Pages; import de.ipvs.as.mbp.domain.access_control.ACAbstractEffect; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestACPolicyController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestACPolicyController.java index 6eaa1e64a..0f85996ea 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestACPolicyController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestACPolicyController.java @@ -12,7 +12,7 @@ import de.ipvs.as.mbp.error.EntityAlreadyExistsException; import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingOwnerPrivilegesException; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.util.C; import de.ipvs.as.mbp.util.Pages; import org.assertj.core.util.Arrays; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorController.java index 4df2fb7f2..4a93ddb5a 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorController.java @@ -11,7 +11,7 @@ import de.ipvs.as.mbp.repository.ActuatorRepository; import de.ipvs.as.mbp.repository.DeviceRepository; import de.ipvs.as.mbp.repository.OperatorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorTypeController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorTypeController.java index c437d45b3..da7cc395b 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorTypeController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestActuatorTypeController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.ActuatorTypeRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.entity_type.ActuatorType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentFilterController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentFilterController.java index 55ef4254b..fa43533cb 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentFilterController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentFilterController.java @@ -14,7 +14,7 @@ import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.*; import de.ipvs.as.mbp.repository.projection.ComponentExcerpt; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentStateController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentStateController.java index 6f7bac1ca..8dfaa4ea3 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentStateController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestComponentStateController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.repository.ActuatorRepository; import de.ipvs.as.mbp.repository.ComponentRepository; import de.ipvs.as.mbp.repository.SensorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.component.Actuator; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeploymentController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeploymentController.java index f1432b4df..e9247ee0d 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeploymentController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeploymentController.java @@ -8,7 +8,7 @@ import de.ipvs.as.mbp.repository.ActuatorRepository; import de.ipvs.as.mbp.repository.ComponentRepository; import de.ipvs.as.mbp.repository.SensorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java index 02dd71452..738011dbf 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceController.java @@ -13,7 +13,7 @@ import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.DeviceRepository; import de.ipvs.as.mbp.repository.KeyPairRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.device.Device; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceStateController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceStateController.java index a5947df54..e9a72274e 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceStateController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceStateController.java @@ -7,7 +7,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.DeviceRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.deployment.DeployerDispatcher; import de.ipvs.as.mbp.service.deployment.DeviceState; import de.ipvs.as.mbp.service.deployment.IDeployer; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceTypeController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceTypeController.java index 8aada598f..46ebe0076 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceTypeController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDeviceTypeController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.DeviceTypeRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.entity_type.DeviceType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestDocumentationController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestDocumentationController.java index 7b77c1826..768b28cc8 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestDocumentationController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestDocumentationController.java @@ -8,7 +8,7 @@ import de.ipvs.as.mbp.RestConfiguration; import de.ipvs.as.mbp.SwaggerConfiguration; import de.ipvs.as.mbp.error.MissingAdminPrivilegesException; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.web.rest.response.DocumentationMetaData; import de.ipvs.as.mbp.web.rest.response.DocumentationURL; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestEnvModelController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestEnvModelController.java index b09555e3d..9f1000b72 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestEnvModelController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestEnvModelController.java @@ -6,7 +6,7 @@ import de.ipvs.as.mbp.error.EnvironmentModelParseException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.EnvironmentModelRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestKeyPairController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestKeyPairController.java index f69d196e8..37168af46 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestKeyPairController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestKeyPairController.java @@ -11,8 +11,8 @@ import de.ipvs.as.mbp.error.MBPException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.KeyPairRepository; -import de.ipvs.as.mbp.service.UserEntityService; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserEntityService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.util.S; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestLogController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestLogController.java index 31525a6b5..289af9351 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestLogController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestLogController.java @@ -4,7 +4,7 @@ import de.ipvs.as.mbp.domain.logs.ExceptionLog; import de.ipvs.as.mbp.error.MissingAdminPrivilegesException; import de.ipvs.as.mbp.repository.ExceptionLogRepository; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringController.java index 80b9de6b1..fb4ee344e 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringController.java @@ -14,8 +14,8 @@ import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.DeviceRepository; import de.ipvs.as.mbp.repository.projection.MonitoringOperatorExcerpt; -import de.ipvs.as.mbp.service.UserEntityService; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserEntityService; +import de.ipvs.as.mbp.service.user.UserService; import de.ipvs.as.mbp.service.deployment.ComponentState; import de.ipvs.as.mbp.web.rest.helper.DeploymentWrapper; import de.ipvs.as.mbp.web.rest.helper.MonitoringHelper; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringOperatorController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringOperatorController.java index 16437576e..fefcbd656 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringOperatorController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestMonitoringOperatorController.java @@ -11,7 +11,7 @@ import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.DeviceTypeRepository; import de.ipvs.as.mbp.repository.MonitoringOperatorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestOperatorController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestOperatorController.java index f93037cca..c3eafb71a 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestOperatorController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestOperatorController.java @@ -9,7 +9,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.OperatorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.hateoas.EntityModel; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleActionController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleActionController.java index 47ab7ac35..912a0dc6f 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleActionController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleActionController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.RuleActionRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.rules.RuleAction; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleController.java index c7b99eeed..416a87acb 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleController.java @@ -13,7 +13,7 @@ import de.ipvs.as.mbp.repository.RuleActionRepository; import de.ipvs.as.mbp.repository.RuleRepository; import de.ipvs.as.mbp.repository.RuleTriggerRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.rules.RuleEngine; import de.ipvs.as.mbp.service.rules.RuleExecutor; import io.swagger.annotations.*; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleTriggerController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleTriggerController.java index 6d52487e0..f04a261d2 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleTriggerController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestRuleTriggerController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.RuleTriggerRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.rules.RuleTrigger; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorController.java index ff7afc720..4fded973a 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorController.java @@ -14,7 +14,7 @@ import de.ipvs.as.mbp.repository.OperatorRepository; import de.ipvs.as.mbp.repository.SensorRepository; import de.ipvs.as.mbp.repository.TestDetailsRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorTypeController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorTypeController.java index e72412d01..942f2bfa5 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorTypeController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestSensorTypeController.java @@ -10,7 +10,7 @@ import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.SensorTypeRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.access_control.ACAccessRequest; import de.ipvs.as.mbp.domain.access_control.ACAccessType; import de.ipvs.as.mbp.domain.entity_type.SensorType; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java index 7ed13d33a..ab2940c5c 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestSettingsController.java @@ -5,7 +5,7 @@ import de.ipvs.as.mbp.domain.settings.Settings; import de.ipvs.as.mbp.error.MBPException; import de.ipvs.as.mbp.error.MissingAdminPrivilegesException; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.settings.DefaultOperatorService; import de.ipvs.as.mbp.service.settings.SettingsService; import de.ipvs.as.mbp.service.testing.DefaultTestingComponents; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestTestingController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestTestingController.java index 4ec1799e8..3954f1178 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestTestingController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestTestingController.java @@ -8,14 +8,13 @@ import de.ipvs.as.mbp.domain.operator.parameters.ParameterInstance; import de.ipvs.as.mbp.domain.rules.Rule; import de.ipvs.as.mbp.domain.testing.TestDetails; -import de.ipvs.as.mbp.domain.testing.TestDetailsCreateValidator; import de.ipvs.as.mbp.domain.testing.TestDetailsDTO; import de.ipvs.as.mbp.error.EntityNotFoundException; import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.RuleRepository; import de.ipvs.as.mbp.repository.SensorRepository; import de.ipvs.as.mbp.repository.TestDetailsRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.testing.TestEngine; import de.ipvs.as.mbp.service.testing.analyzer.TestAnalyzer; import de.ipvs.as.mbp.service.testing.executor.TestExecutor; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java index 0bf365ee3..cc7382e71 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestUserController.java @@ -1,6 +1,5 @@ package de.ipvs.as.mbp.web.rest; -import de.ipvs.as.mbp.AuthCookieFilter; import de.ipvs.as.mbp.RestConfiguration; import de.ipvs.as.mbp.constants.Constants; import de.ipvs.as.mbp.domain.user.User; @@ -8,7 +7,8 @@ import de.ipvs.as.mbp.error.*; import de.ipvs.as.mbp.repository.UserRepository; import de.ipvs.as.mbp.repository.projection.UserExcerpt; -import de.ipvs.as.mbp.service.UserService; +import de.ipvs.as.mbp.service.user.UserService; +import de.ipvs.as.mbp.service.user.UserSessionService; import de.ipvs.as.mbp.util.Pages; import io.swagger.annotations.*; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +40,9 @@ public class RestUserController { @Autowired private UserService userService; + @Autowired + private UserSessionService userSessionService; + @GetMapping(produces = "application/hal+json") @ApiOperation(value = "Retrieves all existing users.", notes = "Requires admin privileges.", produces = "application/hal+json") @@ -103,18 +106,15 @@ public ResponseEntity login(@RequestBody @ApiParam(value = "Login data", r } // Check password - if (userService.checkPassword(user.getId(), loginData.getPassword())) { - - ResponseCookie cookie = ResponseCookie - .from(AuthCookieFilter.COOKIE_NAME, "asdfasdfsession234") - .maxAge(999999).sameSite("Strict") - .path("/").httpOnly(true).secure(true).build(); - - return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) - .body(user); - } else { + if (!userService.checkPassword(user.getId(), loginData.getPassword())) { throw new InvalidPasswordException(); } + + //Create new session and retrieve corresponding cookie + ResponseCookie sessionCookie = userSessionService.createSessionCookie(user); + + //Build response from cookie + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, sessionCookie.toString()).body(user); } @DeleteMapping(path = "/{userId}") diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogController.java index ea7f4610f..2ff9335b8 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogController.java @@ -17,7 +17,7 @@ import de.ipvs.as.mbp.repository.SensorRepository; import de.ipvs.as.mbp.repository.ValueLogRepository; import de.ipvs.as.mbp.service.UnitConverterService; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.access_control.ACEffectService; import de.ipvs.as.mbp.util.S; import de.ipvs.as.mbp.web.rest.helper.MonitoringHelper; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogStatsController.java b/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogStatsController.java index 3ae8a244a..49d16d5fa 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogStatsController.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/RestValueLogStatsController.java @@ -14,7 +14,7 @@ import de.ipvs.as.mbp.error.MissingPermissionException; import de.ipvs.as.mbp.repository.ActuatorRepository; import de.ipvs.as.mbp.repository.SensorRepository; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.service.access_control.ACEffectService; import de.ipvs.as.mbp.service.stats.ValueLogStatsService; import de.ipvs.as.mbp.service.stats.model.ValueLogStats; diff --git a/src/main/java/de/ipvs/as/mbp/web/rest/helper/MonitoringHelper.java b/src/main/java/de/ipvs/as/mbp/web/rest/helper/MonitoringHelper.java index 1b96d7362..e4493c49d 100644 --- a/src/main/java/de/ipvs/as/mbp/web/rest/helper/MonitoringHelper.java +++ b/src/main/java/de/ipvs/as/mbp/web/rest/helper/MonitoringHelper.java @@ -13,7 +13,7 @@ import de.ipvs.as.mbp.repository.DeviceRepository; import de.ipvs.as.mbp.repository.MonitoringOperatorRepository; import de.ipvs.as.mbp.repository.projection.MonitoringOperatorExcerpt; -import de.ipvs.as.mbp.service.UserEntityService; +import de.ipvs.as.mbp.service.user.UserEntityService; import de.ipvs.as.mbp.domain.monitoring.MonitoringOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/resources/static/js/services/UserService.js b/src/main/resources/static/js/services/UserService.js index dc7cceb98..f74079200 100644 --- a/src/main/resources/static/js/services/UserService.js +++ b/src/main/resources/static/js/services/UserService.js @@ -5,7 +5,6 @@ app.factory('UserService', ['$rootScope', '$location', '$cookieStore', 'HttpServ function ($rootScope, $location, $cookieStore, HttpService, ENDPOINT_URI) { const URL_LOGIN_SUFFIX = '/users/login'; - const URL_LOGOUT_SUFFIX = 'users/logout'; const URL_PROMOTE_SUFFIX = '/promote'; const URL_DEGRADE_SUFFIX = '/degrade'; const URL_CHANGE_PASSWORD_SUFFIX = '/change_password'; diff --git a/src/main/webapp/WEB-INF/views/index.html b/src/main/webapp/WEB-INF/views/index.html index 5443075ba..96c8c9721 100644 --- a/src/main/webapp/WEB-INF/views/index.html +++ b/src/main/webapp/WEB-INF/views/index.html @@ -131,7 +131,7 @@ MBP: A Platform for Managing IoT Environments
- + power_settings_new Logout From 5e5af1e0dac46b20c14a145bb653a73e33b42a72 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Tue, 1 Jun 2021 01:34:54 +0200 Subject: [PATCH 4/9] Finished user session system --- pom.xml | 37 +++-- .../java/de/ipvs/as/mbp/AuthCookieFilter.java | 73 ---------- .../de/ipvs/as/mbp/SecurityConfiguration.java | 40 ++---- .../ipvs/as/mbp/UserSessionCookieFilter.java | 129 ++++++++++++++++++ ...Auth2AuthorizationServerConfiguration.java | 15 +- .../service/user/UserDetailsServiceImpl.java | 34 ----- .../mbp/service/user/UserSessionService.java | 91 +++++++++++- .../WEB-INF/views/templates/settings.html | 17 +-- 8 files changed, 264 insertions(+), 172 deletions(-) delete mode 100644 src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java create mode 100644 src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java delete mode 100644 src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java diff --git a/pom.xml b/pom.xml index 7ab2305b1..21c05bb1d 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,9 @@ https://jcenter.bintray.com - + + org.springframework.boot @@ -71,6 +72,12 @@ 1.2.5 + + + com.github.ben-manes.caffeine + caffeine + + com.espertech @@ -131,21 +138,21 @@ - - - - com.itextpdf - itext7-core - 7.1.10 - pom - + + + + com.itextpdf + itext7-core + 7.1.10 + pom + - - - org.jfree - jfreechart - 1.5.0 - + + + org.jfree + jfreechart + 1.5.0 + diff --git a/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java b/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java deleted file mode 100644 index 859947ba8..000000000 --- a/src/main/java/de/ipvs/as/mbp/AuthCookieFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -package de.ipvs.as.mbp; - -import de.ipvs.as.mbp.domain.user.User; -import de.ipvs.as.mbp.repository.UserSessionRepository; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.web.filter.GenericFilterBean; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - - -public class AuthCookieFilter extends GenericFilterBean implements LogoutSuccessHandler { - - public final static String COOKIE_NAME = "authentication"; - - private UserSessionRepository userSessionRepository; - - public AuthCookieFilter(UserSessionRepository userSessionRepository) { - this.userSessionRepository = userSessionRepository; - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - - HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; - String sessionId = extractAuthenticationCookie(httpServletRequest); - - if (sessionId != null) { - User user = new User(); - user.setAdmin(true); - user.setUsername("admin"); - user.setFirstName("Test"); - user.setLastName("User"); - UserAuthentication userAuthentication = new UserAuthentication(user); - SecurityContextHolder.getContext().setAuthentication(userAuthentication); - } - - filterChain.doFilter(servletRequest, servletResponse); - } - - public static String extractAuthenticationCookie(HttpServletRequest httpServletRequest) { - String sessionId = null; - Cookie[] cookies = httpServletRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals(AuthCookieFilter.COOKIE_NAME)) { - sessionId = cookie.getValue(); - break; - } - } - } - return sessionId; - } - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - System.out.println("asdf"); - - String sessionId = extractAuthenticationCookie(request); - //TODO delete session - - response.sendRedirect(request.getContextPath() + SecurityConfiguration.URL_LOGIN); - } -} \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java index f6e04cf30..cbd524e4c 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java @@ -1,14 +1,12 @@ package de.ipvs.as.mbp; -import de.ipvs.as.mbp.repository.UserSessionRepository; -import de.ipvs.as.mbp.service.user.UserDetailsServiceImpl; -import org.springframework.beans.factory.BeanInitializationException; +import de.ipvs.as.mbp.service.user.UserSessionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -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.builders.WebSecurity; @@ -16,7 +14,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.HttpStatusEntryPoint; @@ -24,6 +21,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration +@Import(UserSessionService.class) @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @@ -32,16 +30,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public static final String URL_LOGIN = "/login"; public static final String URL_LOGOUT = "/logout"; - private final AuthCookieFilter authCookieFilter; + //Cookie filter for validating session cookies + private final UserSessionCookieFilter userSessionCookieFilter; @Autowired - public SecurityConfiguration(UserSessionRepository userSessionRepository) { - this.authCookieFilter = new AuthCookieFilter(userSessionRepository); - } - - @Bean - public UserDetailsService mongoUserDetails() { - return new UserDetailsServiceImpl(); + public SecurityConfiguration(UserSessionService userSessionService) { + this.userSessionCookieFilter = new UserSessionCookieFilter(userSessionService); } @Bean @@ -49,16 +43,6 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Override - public void configure(AuthenticationManagerBuilder auth) { - try { - UserDetailsService userDetailsService = mongoUserDetails(); - auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - } catch (Exception e) { - throw new BeanInitializationException("Security configuration failed", e); - } - } - @Override public void configure(WebSecurity web) throws Exception { web.ignoring() @@ -67,7 +51,6 @@ public void configure(WebSecurity web) throws Exception { .antMatchers("/webapp/**") .antMatchers(URL_LOGIN, "/templates/register") .antMatchers(HttpMethod.GET, RestConfiguration.BASE_PATH + "/settings/mbpinfo") - .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/users/authenticate") .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/users") .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/checkOauthTokenUser") .antMatchers(HttpMethod.POST, RestConfiguration.BASE_PATH + "/checkOauthTokenSuperuser") @@ -76,20 +59,21 @@ public void configure(WebSecurity web) throws Exception { @Override protected void configure(HttpSecurity http) throws Exception { + //Configure HTTP security http .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(AbstractHttpConfigurer::disable) .logout(c -> { c.logoutRequestMatcher(new AntPathRequestMatcher(URL_LOGOUT)); - c.deleteCookies(AuthCookieFilter.COOKIE_NAME); - c.logoutSuccessHandler(authCookieFilter); + c.deleteCookies(UserSessionCookieFilter.SESSION_COOKIE_NAME); + c.logoutSuccessHandler(userSessionCookieFilter); }) .authorizeRequests(c -> { - c.antMatchers("/api/authenticate").permitAll(); + c.antMatchers("/api/users/login").permitAll(); c.antMatchers("/api/**").authenticated(); }) .exceptionHandling(c -> c .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) - .addFilterAfter(this.authCookieFilter, SecurityContextPersistenceFilter.class); + .addFilterAfter(userSessionCookieFilter, SecurityContextPersistenceFilter.class); } } diff --git a/src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java b/src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java new file mode 100644 index 000000000..869a3c6af --- /dev/null +++ b/src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java @@ -0,0 +1,129 @@ +package de.ipvs.as.mbp; + +import de.ipvs.as.mbp.domain.user.User; +import de.ipvs.as.mbp.service.user.UserSessionService; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +/** + * Objects of this class behaves as filter for user session cookies by exploiting functions offered by + * a user session service that allows to manage user sessions. + */ +public class UserSessionCookieFilter extends GenericFilterBean implements LogoutSuccessHandler { + + //Name to use for the session cookie + public final static String SESSION_COOKIE_NAME = "user_session"; + + private UserSessionService userSessionService; + + /** + * Creates a new filter for user session cookies by passing a reference to a service that allows the + * management of user sessions. + * + * @param userSessionService The user session service to use + */ + public UserSessionCookieFilter(UserSessionService userSessionService) { + this.userSessionService = userSessionService; + } + + /** + * Applies the cookie filter to incoming HTTP requests by checking whether a session ID is provided as cookie + * and looking it up into the user session repository. If it exists, a authorization is set corresponding + * to the user to which the session belongs. + * + * @param servletRequest The incoming HTTP request + * @param servletResponse The intended response to the HTTP request + * @param filterChain The current filter chain + * @throws IOException In case of an I/O error + * @throws ServletException In case of an servlet error + */ + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws ServletException, IOException { + + //Get session of the request and try to find the associated user + Optional userOptional = getUserFromRequest((HttpServletRequest) servletRequest); + + //Check if user could be found + if (userOptional.isPresent()) { + //Create and set corresponding authentication object + UserAuthentication userAuthentication = new UserAuthentication(userOptional.get()); + SecurityContextHolder.getContext().setAuthentication(userAuthentication); + } + + //Execute remainder of filter chain + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + //Retrieve session ID from request cookie + String sessionId = getSessionIdFromRequest(request); + + //Invalidate session + userSessionService.invalidateSession(sessionId); + + //Reply with redirect to login page + response.sendRedirect(request.getContextPath() + SecurityConfiguration.URL_LOGIN); + } + + /** + * Extracts the session ID from the corresponding cookie of a given HttpServletRequest and looks up the + * user that is associated with this session. + * + * @param request The HttpServletRequest to extract the user for + * @return Optional containing the associated user (if existing) + */ + private Optional getUserFromRequest(HttpServletRequest request) { + //Extract session ID (if existing) from HTTP request + String sessionId = getSessionIdFromRequest(request); + + //Check if session ID could be found + if ((sessionId == null) || sessionId.isEmpty()) { + return Optional.empty(); + } + + //Try to find user that is associated with the session (if exists) + return userSessionService.getUserBySessionId(sessionId); + } + + /** + * Extracts and returns the session ID from the corresponding cookie of a given HttpServletRequest. + * + * @param request The HttpServletRequest to extract the session ID for + * @return The extracted session ID or null in case the cookie is not present + */ + private String getSessionIdFromRequest(HttpServletRequest request) { + //Retrieve all cookies from the request + Cookie[] cookies = request.getCookies(); + + //Check if cookies are available + if (cookies == null) { + return null; + } + + //Iterate over all cookies + for (Cookie cookie : cookies) { + //Check for matching cookie name + if (cookie.getName().equals(UserSessionCookieFilter.SESSION_COOKIE_NAME)) { + //Cookie name matches, thus return its value + return cookie.getValue(); + } + } + + //No matching cookie available + return null; + } +} \ No newline at end of file diff --git a/src/main/java/de/ipvs/as/mbp/security/oauth2/OAuth2AuthorizationServerConfiguration.java b/src/main/java/de/ipvs/as/mbp/security/oauth2/OAuth2AuthorizationServerConfiguration.java index 1e6c65339..22c6ac2b0 100644 --- a/src/main/java/de/ipvs/as/mbp/security/oauth2/OAuth2AuthorizationServerConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/security/oauth2/OAuth2AuthorizationServerConfiguration.java @@ -1,14 +1,10 @@ package de.ipvs.as.mbp.security.oauth2; -import java.util.Collections; - import de.ipvs.as.mbp.constants.Constants; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; @@ -20,6 +16,8 @@ import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import java.util.Collections; + @Configuration @EnableAuthorizationServer @PropertySource(value = "classpath:application.properties") @@ -31,12 +29,6 @@ public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerC @Value("${security.oauth2.client.pre-established-redirect-uri}") private String redirectUri; - private final UserDetailsService userDetailsService; - - public OAuth2AuthorizationServerConfiguration(@Qualifier("mongoUserDetails") UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() @@ -68,8 +60,7 @@ public void configure(AuthorizationServerEndpointsConfigurer endpoints) { tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter())); endpoints .tokenStore(tokenStore()) - .accessTokenConverter(accessTokenConverter()) - .userDetailsService(userDetailsService); + .accessTokenConverter(accessTokenConverter()); } @Bean diff --git a/src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java b/src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java deleted file mode 100644 index cf8590bd0..000000000 --- a/src/main/java/de/ipvs/as/mbp/service/user/UserDetailsServiceImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.ipvs.as.mbp.service.user; - -import java.util.Collections; -import java.util.Locale; -import java.util.Optional; - -import de.ipvs.as.mbp.domain.user.User; -import de.ipvs.as.mbp.repository.UserRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Component; - -/** - * Load a user from the database. - * @author Imeri Amil - */ -@Component -public class UserDetailsServiceImpl implements UserDetailsService { - - @Autowired - private UserRepository userRepository; - - @Override - public UserDetails loadUserByUsername(final String username) { - String lowercaseUsername = username.toLowerCase(Locale.ENGLISH); - Optional userFromDatabase = userRepository.findByUsername(lowercaseUsername); - return userFromDatabase.map(user -> { - return new org.springframework.security.core.userdetails.User(lowercaseUsername, - user.getPassword(), Collections.emptyList()); - }).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseUsername + " was not found in the database")); - } -} diff --git a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java index dc00bf9e9..93f732ff6 100644 --- a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java @@ -1,6 +1,8 @@ package de.ipvs.as.mbp.service.user; -import de.ipvs.as.mbp.AuthCookieFilter; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import de.ipvs.as.mbp.UserSessionCookieFilter; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.domain.user.UserSession; import de.ipvs.as.mbp.repository.UserRepository; @@ -10,6 +12,8 @@ import org.springframework.stereotype.Service; import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * Service class for managing user sessions. @@ -23,17 +27,100 @@ public class UserSessionService { @Autowired private UserSessionRepository userSessionRepository; + //Cache users that are associated with session IDs + private final Cache sessionCache; + + /** + * Creates the user session service. + */ + public UserSessionService() { + //Create and configure cache + this.sessionCache = Caffeine.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).maximumSize(1000).build(); + } + + /** + * Returns the user that is associated with a session, given by its session ID. + * + * @param sessionId The ID of the session to retrieve the user for + * @return Optional containing the user that is associated with the session (if existing) + */ + public Optional getUserBySessionId(String sessionId) { + //Lookup user from cache + User cachedUser = this.sessionCache.getIfPresent(sessionId); + + //Check whether the cache entry existed + if (cachedUser != null) { + return Optional.of(cachedUser); + } + + //User is not cached, retrieve corresponding session from repository + Optional userSessionOptional = userSessionRepository.findFirstBySessionId(sessionId); + + //Check if session exists + if ((!userSessionOptional.isPresent())) { + return Optional.empty(); + } + + //Get user ID from session + String userId = userSessionOptional.get().getUserId(); + + //Check if user ID is available + if ((userId == null) || userId.isEmpty()) { + return Optional.empty(); + } + + //Retrieve corresponding user by its ID + Optional userOptional = userRepository.findById(userId); + + //Update cache if user was found + userOptional.ifPresent(user -> sessionCache.put(sessionId, user)); + + //Return user optional + return userOptional; + } + + /** + * Invalidates a session by removing it from the repository. + * + * @param sessionId The ID of the session to invalidate + */ + public void invalidateSession(String sessionId) { + //Delete session from repository + userSessionRepository.deleteById(sessionId); + + //Invalidate entry in cache + sessionCache.invalidate(sessionId); + } + + /** + * Creates and saves a new session for a given user and returns a cookie representing + * this session by its session ID. + * + * @param user The user to create the session for + * @return The resulting cookie containing the session ID + */ public ResponseCookie createSessionCookie(User user) { + //Sanity check + if (user == null) { + throw new IllegalArgumentException("User must not be null."); + } + //Create new session for given user UserSession userSession = createSession(user); //Create corresponding cookie from session return ResponseCookie - .from(AuthCookieFilter.COOKIE_NAME, userSession.getSessionId()) + .from(UserSessionCookieFilter.SESSION_COOKIE_NAME, userSession.getSessionId()) .maxAge(Duration.ofDays(30)).sameSite("strict").httpOnly(true).secure(true) .path("/").build(); } + /** + * Creates and stores a new session for a given user. + * + * @param user The user to create the session for + * @return THe created and stored session + */ private UserSession createSession(User user) { //Sanity check if (user == null) { diff --git a/src/main/webapp/WEB-INF/views/templates/settings.html b/src/main/webapp/WEB-INF/views/templates/settings.html index d9c48347d..43d835ef0 100644 --- a/src/main/webapp/WEB-INF/views/templates/settings.html +++ b/src/main/webapp/WEB-INF/views/templates/settings.html @@ -9,7 +9,6 @@

-

Default operators

@@ -108,15 +107,17 @@

Reinstall and Redeploy Testing Default Components<
- - + +
-

+
From c5f3915e201bcf92ebbb545491d4c05abbbe0d43 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Tue, 1 Jun 2021 15:21:00 +0200 Subject: [PATCH 5/9] Added scheduled method for expiring sessions, re-ref to login page --- src/main/java/de/ipvs/as/mbp/Initializer.java | 4 ---- .../java/de/ipvs/as/mbp/MBPApplication.java | 13 ----------- .../de/ipvs/as/mbp/SecurityConfiguration.java | 1 + .../ipvs/as/mbp/SecurityWebInitializer.java | 11 --------- .../ipvs/as/mbp/WebServletConfiguration.java | 3 ++- .../{ => security}/UserAuthentication.java | 2 +- .../UserSessionCookieFilter.java | 3 ++- .../mbp/service/user/UserSessionService.java | 23 +++++++++++++++++-- .../static/js/services/HttpService.js | 9 ++++++-- 9 files changed, 34 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/de/ipvs/as/mbp/MBPApplication.java delete mode 100644 src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java rename src/main/java/de/ipvs/as/mbp/{ => security}/UserAuthentication.java (97%) rename src/main/java/de/ipvs/as/mbp/{ => security}/UserSessionCookieFilter.java (98%) diff --git a/src/main/java/de/ipvs/as/mbp/Initializer.java b/src/main/java/de/ipvs/as/mbp/Initializer.java index 485f33ce5..cfbef1e48 100644 --- a/src/main/java/de/ipvs/as/mbp/Initializer.java +++ b/src/main/java/de/ipvs/as/mbp/Initializer.java @@ -7,10 +7,6 @@ import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; -/** - * - * @author rafaelkperes - */ public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer { diff --git a/src/main/java/de/ipvs/as/mbp/MBPApplication.java b/src/main/java/de/ipvs/as/mbp/MBPApplication.java deleted file mode 100644 index 4655953c7..000000000 --- a/src/main/java/de/ipvs/as/mbp/MBPApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.ipvs.as.mbp; - -import org.springframework.boot.SpringApplication; - -//@SpringBootApplication -//@EnableAutoConfiguration -public class MBPApplication { - - public static void main(String[] args) { - SpringApplication.run(MBPApplication.class, args); - } - -} diff --git a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java index cbd524e4c..75d0d7062 100644 --- a/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/SecurityConfiguration.java @@ -1,5 +1,6 @@ package de.ipvs.as.mbp; +import de.ipvs.as.mbp.security.UserSessionCookieFilter; import de.ipvs.as.mbp.service.user.UserSessionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java b/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java deleted file mode 100644 index 956cb4962..000000000 --- a/src/main/java/de/ipvs/as/mbp/SecurityWebInitializer.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.ipvs.as.mbp; - -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; - -/** - * Security Web Initializer needed for security configuration. - * - */ -public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{ - -} diff --git a/src/main/java/de/ipvs/as/mbp/WebServletConfiguration.java b/src/main/java/de/ipvs/as/mbp/WebServletConfiguration.java index 42a8453fc..b4062a978 100644 --- a/src/main/java/de/ipvs/as/mbp/WebServletConfiguration.java +++ b/src/main/java/de/ipvs/as/mbp/WebServletConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; @@ -32,7 +33,7 @@ Constants.ROOT_PACKAGE }) @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) -public class WebServletConfiguration implements WebMvcConfigurer { +public class WebServletConfiguration extends AbstractSecurityWebApplicationInitializer implements WebMvcConfigurer { @Autowired private ApplicationContext applicationContext; diff --git a/src/main/java/de/ipvs/as/mbp/UserAuthentication.java b/src/main/java/de/ipvs/as/mbp/security/UserAuthentication.java similarity index 97% rename from src/main/java/de/ipvs/as/mbp/UserAuthentication.java rename to src/main/java/de/ipvs/as/mbp/security/UserAuthentication.java index 355327387..2004cc9a3 100644 --- a/src/main/java/de/ipvs/as/mbp/UserAuthentication.java +++ b/src/main/java/de/ipvs/as/mbp/security/UserAuthentication.java @@ -1,4 +1,4 @@ -package de.ipvs.as.mbp; +package de.ipvs.as.mbp.security; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.domain.user.UserLoginData; diff --git a/src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java b/src/main/java/de/ipvs/as/mbp/security/UserSessionCookieFilter.java similarity index 98% rename from src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java rename to src/main/java/de/ipvs/as/mbp/security/UserSessionCookieFilter.java index 869a3c6af..a2af7126e 100644 --- a/src/main/java/de/ipvs/as/mbp/UserSessionCookieFilter.java +++ b/src/main/java/de/ipvs/as/mbp/security/UserSessionCookieFilter.java @@ -1,5 +1,6 @@ -package de.ipvs.as.mbp; +package de.ipvs.as.mbp.security; +import de.ipvs.as.mbp.SecurityConfiguration; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.service.user.UserSessionService; import org.springframework.security.core.Authentication; diff --git a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java index 93f732ff6..04d239113 100644 --- a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java @@ -2,21 +2,25 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import de.ipvs.as.mbp.UserSessionCookieFilter; import de.ipvs.as.mbp.domain.user.User; import de.ipvs.as.mbp.domain.user.UserSession; import de.ipvs.as.mbp.repository.UserRepository; import de.ipvs.as.mbp.repository.UserSessionRepository; +import de.ipvs.as.mbp.security.UserSessionCookieFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseCookie; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Optional; import java.util.concurrent.TimeUnit; /** - * Service class for managing user sessions. + * Service class for managing user sessions that is also responsible for periodically cleaning the session repository + * by deleting old user sessions. */ @Service public class UserSessionService { @@ -133,4 +137,19 @@ private UserSession createSession(User user) { //Store session in repository return userSessionRepository.insert(userSession); } + + /** + * Runs once a day and removes all user sessions from the session repository that are older than 7 days. + */ + @Scheduled(fixedRate = 1000 * 60 * 60 * 24) + private void cleanupSessionRepository() { + //Iterate over all stored user sessions + for (UserSession userSession : userSessionRepository.findAll()) { + //Check if user session is older than 7 days + if (userSession.getCreated().plus(7, ChronoUnit.DAYS).isBefore(Instant.now())) { + //Invalidate session + invalidateSession(userSession.getSessionId()); + } + } + } } \ No newline at end of file diff --git a/src/main/resources/static/js/services/HttpService.js b/src/main/resources/static/js/services/HttpService.js index ac7302630..dc071bae7 100644 --- a/src/main/resources/static/js/services/HttpService.js +++ b/src/main/resources/static/js/services/HttpService.js @@ -3,8 +3,8 @@ /** * Provides services for HTTP requests. */ -app.factory('HttpService', ['$rootScope', '$interval', 'ENDPOINT_URI', 'NotificationService', - function ($rootScope, $interval, ENDPOINT_URI, NotificationService) { +app.factory('HttpService', ['$rootScope', '$location', '$interval', 'ENDPOINT_URI', 'NotificationService', + function ($rootScope, $location, $interval, ENDPOINT_URI, NotificationService) { //Enables or disables the debug mode const debugMode = true; @@ -269,8 +269,13 @@ app.factory('HttpService', ['$rootScope', '$interval', 'ENDPOINT_URI', 'Notifica if (errorMessage != null) { NotificationService.showError(errorMessage); } else if (response.status === 0) { + //Request could not be executed NotificationService.showError("Request to backend failed. Is it online?"); + } else if (response.status === 401) { + //Unauthorized, i.e. user is probably not logged in + $location.path('/login'); } else { + //Unknown error source NotificationService.showError("Request was not successful."); } From 4742af79b93eee6d41857f5ab123a96c815a5980 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 10 Jun 2021 15:33:26 +0200 Subject: [PATCH 6/9] Added version number to dependencies, removed secure field from cookie --- pom.xml | 1 + .../java/de/ipvs/as/mbp/service/user/UserSessionService.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21c05bb1d..71dfa48dd 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ com.github.ben-manes.caffeine caffeine + 3.0.2 diff --git a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java index 04d239113..57c572375 100644 --- a/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java +++ b/src/main/java/de/ipvs/as/mbp/service/user/UserSessionService.java @@ -115,7 +115,7 @@ public ResponseCookie createSessionCookie(User user) { //Create corresponding cookie from session return ResponseCookie .from(UserSessionCookieFilter.SESSION_COOKIE_NAME, userSession.getSessionId()) - .maxAge(Duration.ofDays(30)).sameSite("strict").httpOnly(true).secure(true) + .maxAge(Duration.ofDays(30)).sameSite("strict").httpOnly(true) .path("/").build(); } From 6103ede691ce55b1f39ce56ba8278220567845c9 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 10 Jun 2021 15:46:25 +0200 Subject: [PATCH 7/9] Fixed loading image on login page --- .../resources/static/js/controllers/user/LoginController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/js/controllers/user/LoginController.js b/src/main/resources/static/js/controllers/user/LoginController.js index 75faf29f5..4a2079329 100644 --- a/src/main/resources/static/js/controllers/user/LoginController.js +++ b/src/main/resources/static/js/controllers/user/LoginController.js @@ -22,7 +22,7 @@ app.controller('LoginController', ['$scope', '$location', 'UserService', vm.dataLoading = true; //Perform login - UserService.loginUser(vm.username, vm.password).then(function (userData) { + UserService.loginUser(vm.username, vm.password).finally(function () { //Hide loader vm.dataLoading = false; $scope.$apply(); From 9a3a1638769c7d289db05e9d3c7778c52c970520 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 10 Jun 2021 15:50:19 +0200 Subject: [PATCH 8/9] Another fix --- .../resources/static/js/controllers/user/LoginController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/js/controllers/user/LoginController.js b/src/main/resources/static/js/controllers/user/LoginController.js index 4a2079329..d7a12d61c 100644 --- a/src/main/resources/static/js/controllers/user/LoginController.js +++ b/src/main/resources/static/js/controllers/user/LoginController.js @@ -22,7 +22,7 @@ app.controller('LoginController', ['$scope', '$location', 'UserService', vm.dataLoading = true; //Perform login - UserService.loginUser(vm.username, vm.password).finally(function () { + UserService.loginUser(vm.username, vm.password).always(function () { //Hide loader vm.dataLoading = false; $scope.$apply(); From e7b4a456f9a1259760607ad28bf741bec93f9708 Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 10 Jun 2021 15:55:25 +0200 Subject: [PATCH 9/9] Caffeeine dependency version did not match target java version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71dfa48dd..2b02baa84 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ com.github.ben-manes.caffeine caffeine - 3.0.2 + 2.9.1