diff --git a/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java b/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java index fab183d..bdbabf5 100644 --- a/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java +++ b/chatto/src/main/java/org/ros/chatto/BeanConfigurations.java @@ -1,6 +1,7 @@ package org.ros.chatto; import org.modelmapper.ModelMapper; +import org.ros.chatto.logged.ActiveUserStore; import org.ros.chatto.security.AuthenticationSuccessHandlerImpl; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; @@ -44,6 +45,11 @@ public class BeanConfigurations { return messageSource; } + @Bean + ActiveUserStore activeUserStore() { + return new ActiveUserStore(); + } + // @Bean // public Connection connection() throws SQLException // { diff --git a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java index 8c4b774..de665a8 100644 --- a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java +++ b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java @@ -1,14 +1,14 @@ package org.ros.chatto; +import org.ros.chatto.logged.MyLogoutSuccessHandler; +import org.ros.chatto.logged.MySimpleUrlAuthenticationSuccessHandler; import org.ros.chatto.security.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -17,8 +17,7 @@ import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @Configuration @EnableWebSecurity @@ -96,8 +95,13 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Configuration @Order(2) public static class FormWebSecurity extends WebSecurityConfigurerAdapter { + @Autowired + private MySimpleUrlAuthenticationSuccessHandler mySimpleUrlAuthenticationSuccessHandler; + + @Autowired + private MyLogoutSuccessHandler myLogoutSuccessHandler; + @Override - protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() // .antMatchers(HttpMethod.POST, "/api/**").permitAll() @@ -115,7 +119,9 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .and() .formLogin().loginPage("/login").permitAll().loginProcessingUrl("/perform_login") -// .successHandler(authenticationSuccessHandler) + .successHandler(mySimpleUrlAuthenticationSuccessHandler) + .and() + .logout().logoutSuccessHandler(myLogoutSuccessHandler) // .failureUrl("/?login_error") // .and() // .logout().invalidateHttpSession(true) diff --git a/chatto/src/main/java/org/ros/chatto/controller/DemoRestController.java b/chatto/src/main/java/org/ros/chatto/controller/DemoRestController.java index ffdc8f1..b0eeeed 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/DemoRestController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/DemoRestController.java @@ -6,6 +6,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.ros.chatto.dto.ActiveUserDTO; +import org.ros.chatto.logged.ActiveUserStore; import org.ros.chatto.model.ChatMessage; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.MessageCipher; @@ -16,6 +18,7 @@ import org.ros.chatto.repository.RoleRepository; import org.ros.chatto.repository.UserRepository; import org.ros.chatto.repository.UserRepositoryCustom; import org.ros.chatto.repository.UserRoleRepository; +import org.ros.chatto.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -47,7 +50,10 @@ public class DemoRestController { MessageCipherRepository messageCipherRepository; @Autowired ChatMessageRepository chatMessageRepository; - + @Autowired + ActiveUserStore activeUserStore; + @Autowired + private UserService userService; @GetMapping("/users") public List getAllUsers() { return userRepository.findAll(); @@ -121,5 +127,15 @@ public class DemoRestController { } return "redirect:/users"; } - + + @GetMapping(value = "/loggedUsers") + public ActiveUserStore getActiveUsers() { + return activeUserStore; + } + + @GetMapping(value = "/loggedUsers2") + public List getOtherActiveUsers(Principal principal) + { + return userService.getOtherActiveUsers(principal.getName()); + } } diff --git a/chatto/src/main/java/org/ros/chatto/dto/ActiveUserDTO.java b/chatto/src/main/java/org/ros/chatto/dto/ActiveUserDTO.java new file mode 100644 index 0000000..d15ad8e --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/dto/ActiveUserDTO.java @@ -0,0 +1,12 @@ +package org.ros.chatto.dto; + +import java.time.Instant; + +import lombok.Data; + +@Data +public class ActiveUserDTO { + private String userName; + private Boolean online; + private Instant lastActive; +} diff --git a/chatto/src/main/java/org/ros/chatto/logged/ActiveUser.java b/chatto/src/main/java/org/ros/chatto/logged/ActiveUser.java new file mode 100644 index 0000000..b45f132 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/logged/ActiveUser.java @@ -0,0 +1,11 @@ +package org.ros.chatto.logged; + +import java.time.Instant; + +import lombok.Data; + +@Data +public class ActiveUser { + private String userName; + private Instant lastActive; +} diff --git a/chatto/src/main/java/org/ros/chatto/logged/ActiveUserStore.java b/chatto/src/main/java/org/ros/chatto/logged/ActiveUserStore.java new file mode 100644 index 0000000..b0ac80c --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/logged/ActiveUserStore.java @@ -0,0 +1,20 @@ +package org.ros.chatto.logged; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class ActiveUserStore { + + public List users; + + public List activeUsers; + + public ActiveUserStore() { + users = new ArrayList(); + activeUsers = new ArrayList<>(); + } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java b/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java new file mode 100644 index 0000000..ee442f6 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/logged/LoggedUser.java @@ -0,0 +1,96 @@ +package org.ros.chatto.logged; + +import java.time.Instant; +import java.util.List; + +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionEvent; + +import org.ros.chatto.model.ChatUser; +import org.ros.chatto.model.UserSession; +import org.ros.chatto.repository.UserSessionRepository; +import org.ros.chatto.service.UserService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +import lombok.Getter; +import lombok.Setter; + +@Component +@Getter +@Setter +public class LoggedUser implements HttpSessionBindingListener { + + private String username; + private ActiveUserStore activeUserStore; + + public LoggedUser(String username, ActiveUserStore activeUserStore) { + this.username = username; + this.activeUserStore = activeUserStore; + + } + + public LoggedUser() { + } + + @Override + public void valueBound(HttpSessionBindingEvent event) { + UserService userService = getUserService(event); + UserSessionRepository userSessionRepository = getUserSessionRepository(event); + List users = activeUserStore.getUsers(); + List activeUsers = activeUserStore.getActiveUsers(); + LoggedUser user = (LoggedUser) event.getValue(); + if (!users.contains(user.getUsername())) { + users.add(user.getUsername()); + } + + Instant instant = Instant.now(); + boolean found = activeUsers.stream().anyMatch(au -> au.getUserName().equals(user.getUsername())); + if (!found) { + System.out.println("Test found "); + ActiveUser activeUser = new ActiveUser(); + activeUser.setUserName(user.getUsername()); + activeUser.setLastActive(instant); + activeUsers.add(activeUser); + } + ChatUser chatUser = userService.findByUserName(user.getUsername()); + + UserSession userSession = userSessionRepository.findByUserName(user.getUsername()); + + if(userSession == null) + { + userSession = new UserSession(); + } + + userSession.setUser(chatUser); + userSession.setTimeStamp(instant); + userSessionRepository.save(userSession); + } + + @Override + public void valueUnbound(HttpSessionBindingEvent event) { + List users = activeUserStore.getUsers(); + LoggedUser user = (LoggedUser) event.getValue(); + List activeUsers = activeUserStore.getActiveUsers(); + + if (users.contains(user.getUsername())) { + users.remove(user.getUsername()); + } + + activeUsers.removeIf(au -> au.getUserName().equals(user.getUsername())); + } + + private UserService getUserService(HttpSessionEvent se) { + WebApplicationContext context = WebApplicationContextUtils + .getWebApplicationContext(se.getSession().getServletContext()); + return (UserService) context.getBean(UserService.class); + } + + private UserSessionRepository getUserSessionRepository(HttpSessionEvent se) { + WebApplicationContext context = WebApplicationContextUtils + .getWebApplicationContext(se.getSession().getServletContext()); + return (UserSessionRepository) context.getBean(UserSessionRepository.class); + } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/logged/MyLogoutSuccessHandler.java b/chatto/src/main/java/org/ros/chatto/logged/MyLogoutSuccessHandler.java new file mode 100644 index 0000000..107ef1a --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/logged/MyLogoutSuccessHandler.java @@ -0,0 +1,26 @@ +package org.ros.chatto.logged; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +@Component("myLogoutSuccessHandler") +public class MyLogoutSuccessHandler implements LogoutSuccessHandler{ + @Override + public void onLogoutSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + HttpSession session = request.getSession(); + if (session != null){ + session.removeAttribute("user"); + } + response.sendRedirect("/login?logout"); + } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/logged/MySimpleUrlAuthenticationSuccessHandler.java b/chatto/src/main/java/org/ros/chatto/logged/MySimpleUrlAuthenticationSuccessHandler.java new file mode 100644 index 0000000..1f8090a --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/logged/MySimpleUrlAuthenticationSuccessHandler.java @@ -0,0 +1,31 @@ +package org.ros.chatto.logged; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Component("myAuthenticationSuccessHandler") +public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + @Autowired + ActiveUserStore activeUserStore; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) + throws IOException { + HttpSession session = request.getSession(false); + if (session != null) { + LoggedUser user = new LoggedUser(authentication.getName(), activeUserStore); + session.setAttribute("user", user); + } + response.sendRedirect("/chat"); + } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/model/UserSession.java b/chatto/src/main/java/org/ros/chatto/model/UserSession.java new file mode 100644 index 0000000..262a2b5 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/model/UserSession.java @@ -0,0 +1,30 @@ +package org.ros.chatto.model; + +import java.time.Instant; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.ros.chatto.model.ChatUser; + +import lombok.Data; + +@Data +@Entity +@Table(name = "user_sessions") +public class UserSession { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; +// private String sessionId; + @OneToOne + @JoinColumn(name = "user_id") + private ChatUser user; +// private Boolean loggedIn; + private Instant timeStamp; +} diff --git a/chatto/src/main/java/org/ros/chatto/repository/UserRepository.java b/chatto/src/main/java/org/ros/chatto/repository/UserRepository.java index d50b751..ff4e835 100644 --- a/chatto/src/main/java/org/ros/chatto/repository/UserRepository.java +++ b/chatto/src/main/java/org/ros/chatto/repository/UserRepository.java @@ -2,9 +2,11 @@ package org.ros.chatto.repository; import org.ros.chatto.model.ChatUser; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository{ + @Query("select cu from ChatUser cu where cu.userName = ?1") public ChatUser findByUserName(String userName); } diff --git a/chatto/src/main/java/org/ros/chatto/repository/UserSessionRepository.java b/chatto/src/main/java/org/ros/chatto/repository/UserSessionRepository.java new file mode 100644 index 0000000..e5bd784 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/repository/UserSessionRepository.java @@ -0,0 +1,12 @@ +package org.ros.chatto.repository; + +import org.ros.chatto.model.UserSession; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserSessionRepository extends JpaRepository { + @Query("select us from UserSession us where us.user.userName = ?1 ") + public UserSession findByUserName(String userName); +} diff --git a/chatto/src/main/java/org/ros/chatto/service/UserService.java b/chatto/src/main/java/org/ros/chatto/service/UserService.java index 9c0bf91..e218ab8 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserService.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserService.java @@ -2,6 +2,7 @@ package org.ros.chatto.service; import java.util.List; +import org.ros.chatto.dto.ActiveUserDTO; import org.ros.chatto.dto.UserRegistrationDTO; import org.ros.chatto.model.ChatUser; import org.springframework.stereotype.Service; @@ -12,4 +13,6 @@ public interface UserService { public List findAllOtherUsers(String userName); public void registerUser(UserRegistrationDTO userRegistrationDTO); public List getAllRegularUsers(); + public ChatUser findByUserName(String userName); + public List getOtherActiveUsers(String userName); } diff --git a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java index d4f86e9..edfba7f 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java @@ -1,16 +1,24 @@ package org.ros.chatto.service; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.transaction.Transactional; +import org.ros.chatto.dto.ActiveUserDTO; import org.ros.chatto.dto.UserRegistrationDTO; +import org.ros.chatto.logged.ActiveUserStore; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.Role; import org.ros.chatto.model.UserRole; +import org.ros.chatto.model.UserSession; import org.ros.chatto.repository.UserRepository; import org.ros.chatto.repository.UserRepositoryCustom; import org.ros.chatto.repository.UserRoleRepository; +import org.ros.chatto.repository.UserSessionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -31,6 +39,12 @@ public class UserServiceImpl implements UserService{ @Autowired UserRepositoryCustom userRepositoryCustom; + + @Autowired + private UserSessionRepository userSessionRepository; + + @Autowired + private ActiveUserStore activeUserStore; @Override @Transactional @@ -72,4 +86,38 @@ public class UserServiceImpl implements UserService{ return userRoleRepository.getAllRegularUser(); } + + public List getOtherActiveUsers(String userName) { + List userList = findAllOtherUsers(userName); + List onlineUsers = activeUserStore.getUsers(); + List activeUserDTOs = new ArrayList(); + Map lastActiveMap = convertToMap(userSessionRepository.findAll()); + userList.forEach(u -> { + ActiveUserDTO activeUserDTO = new ActiveUserDTO(); + activeUserDTO.setUserName(u); + activeUserDTO.setOnline(false); + activeUserDTO.setLastActive(lastActiveMap.get(u)); + if(onlineUsers.contains(u)) + { + activeUserDTO.setOnline(true); + } + activeUserDTOs.add(activeUserDTO); + }); + return activeUserDTOs; + } + + @Override + public ChatUser findByUserName(String userName) { + // TODO Auto-generated method stub + return userRepository.findByUserName(userName); + } + + private Map convertToMap(List userSessionList) + { + Map userMap = new HashMap<>(); + userSessionList.forEach(us -> { + userMap.put(us.getUser().getUserName(), us.getTimeStamp()); + }); + return userMap; + } }