diff --git a/src/main/java/de/thm/arsnova/aop/AuthorizationAdviser.java b/src/main/java/de/thm/arsnova/aop/AuthorizationAdviser.java index 8fe9ae26156001ee21b3a1e12c4cb26d372be393..9a24f3176de9d3fd65e2eff2e2e971975b73e95b 100644 --- a/src/main/java/de/thm/arsnova/aop/AuthorizationAdviser.java +++ b/src/main/java/de/thm/arsnova/aop/AuthorizationAdviser.java @@ -2,7 +2,6 @@ package de.thm.arsnova.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; -import org.springframework.security.core.context.SecurityContextHolder; import de.thm.arsnova.annotation.Authenticated; import de.thm.arsnova.entities.User; @@ -25,7 +24,7 @@ public class AuthorizationAdviser { */ @Before("execution(public * de.thm.arsnova.services.*.*(..)) && @annotation(authenticated) && this(object)") public void checkAuthorization(Authenticated authenticated, Object object) { - User u = userService.getUser(SecurityContextHolder.getContext().getAuthentication()); + User u = userService.getCurrentUser(); if (u == null) throw new UnauthorizedException(); // TODO: For unauthorized users e.g. after logout there is still a user object with username 'anonymous' if (u.getUsername().equals("anonymous")) throw new UnauthorizedException(); diff --git a/src/main/java/de/thm/arsnova/controller/FeedbackController.java b/src/main/java/de/thm/arsnova/controller/FeedbackController.java index 9e3a7a340831c2a3d05a84b4e66220f71f22affa..f816e93f4a06aa9e129a2b2b5186394241020e8d 100644 --- a/src/main/java/de/thm/arsnova/controller/FeedbackController.java +++ b/src/main/java/de/thm/arsnova/controller/FeedbackController.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -61,7 +60,7 @@ public class FeedbackController extends AbstractController { @RequestMapping(value="/session/{sessionkey}/feedback", method=RequestMethod.POST) @ResponseBody public Feedback postFeedback(@PathVariable String sessionkey, @RequestBody int value, HttpServletResponse response) { - User user = userService.getUser(SecurityContextHolder.getContext().getAuthentication()); + User user = userService.getCurrentUser(); if (sessionService.saveFeedback(sessionkey, value, user)) { Feedback feedback = sessionService.getFeedback(sessionkey); if (feedback != null) { diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java index 083bdcf47dbabfbbf5b2b2f4171a15a756dc6635..e55c8386ce30e7d81f225b5a98237ff17683f230 100644 --- a/src/main/java/de/thm/arsnova/controller/LoginController.java +++ b/src/main/java/de/thm/arsnova/controller/LoginController.java @@ -112,7 +112,7 @@ public class LoginController extends AbstractController { @RequestMapping(method = RequestMethod.GET, value = "/whoami") @ResponseBody public User whoami() { - return userService.getUser(SecurityContextHolder.getContext().getAuthentication()); + return userService.getCurrentUser(); } @RequestMapping(method = RequestMethod.GET, value = "/logout") diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index de3fee32b43f564b443a56077121a7270d8c0a82..d514b213f966acb043ab4716fdcb59db09f192fb 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -29,7 +29,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -64,20 +63,20 @@ public class SessionController extends AbstractController { @RequestMapping(method = RequestMethod.POST, value = "/authorize") public void authorize(@RequestBody Object sessionObject, HttpServletResponse response) { - String sessionkey = (String) JSONObject.fromObject(sessionObject).get("session"); - if(sessionkey == null) { + String socketid = (String) JSONObject.fromObject(sessionObject).get("session"); + if(socketid == null) { return; } - User u = userService.getUser(SecurityContextHolder.getContext().getAuthentication()); - logger.info("authorize session: " + sessionkey + ", user is: " + u); + User u = userService.getCurrentUser(); + logger.info("authorize session: " + socketid + ", user is: " + u); response.setStatus(u != null ? HttpStatus.CREATED.value() : HttpStatus.UNAUTHORIZED.value()); - server.authorize(UUID.fromString(sessionkey), u); + userService.putUser2SessionID(UUID.fromString(socketid), u); } @RequestMapping(value="/session/{sessionkey}", method=RequestMethod.GET) @ResponseBody - public Session getSession(@PathVariable String sessionkey) { - return sessionService.getSession(sessionkey); + public Session joinSession(@PathVariable String sessionkey) { + return sessionService.joinSession(sessionkey); } @RequestMapping(value="/session/{sessionkey}/online", method=RequestMethod.POST) @@ -122,7 +121,7 @@ public class SessionController extends AbstractController { @RequestMapping(value={"/mySessions","/session/mysessions"}, method=RequestMethod.GET) @ResponseBody public List<Session> getMySession(HttpServletResponse response) { - String username = userService.getUser(SecurityContextHolder.getContext().getAuthentication()).getUsername(); + String username = userService.getCurrentUser().getUsername(); if(username == null) { response.setStatus(HttpStatus.NOT_FOUND.value()); return null; diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index 68ea763333062375154ac3d5cc175e91dd2dd5c0..505fce7731300e480aee523800642784ebedef1e 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -165,8 +165,10 @@ public class CouchDBDao implements IDatabaseDao { if(result == null) { throw new NotFoundException(); } - if (result.isActive() || result.getCreator().equals(this.actualUserName())) { - sessionService.addUserToSessionMap(this.actualUserName(), keyword); + if ( + result.isActive() + || result.getCreator().equals(userService.getCurrentUser().getUsername()) + ) { return result; } @@ -447,8 +449,7 @@ public class CouchDBDao implements IDatabaseDao { } private String actualUserName() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User user = userService.getUser(authentication); + User user = userService.getCurrentUser(); if(user == null) return null; return user.getUsername(); } diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java index 570617ec367441d52d28534927924aa14d2d98b1..e2f058b1ee15bdbb3423f52609d9b51451e1d8d0 100644 --- a/src/main/java/de/thm/arsnova/services/ISessionService.java +++ b/src/main/java/de/thm/arsnova/services/ISessionService.java @@ -33,15 +33,12 @@ import de.thm.arsnova.entities.User; public interface ISessionService { public void cleanFeedbackVotes(); - public Session getSession(String keyword); + public Session joinSession(String keyword); public Session saveSession(Session session); public Feedback getFeedback(String keyword); public boolean saveFeedback(String keyword, int value, User user); public boolean sessionKeyAvailable(String keyword); public String generateKeyword(); - public void addUserToSessionMap(String username, String keyword); - public boolean isUserInSession(User user, String keyword); - public List<String> getUsersInSession(String keyword); public void broadcastFeedbackChanges(Map<String, Set<String>> affectedUsers, Set<String> allAffectedSessions); public List<Session> getMySessions(String username); diff --git a/src/main/java/de/thm/arsnova/services/IUserService.java b/src/main/java/de/thm/arsnova/services/IUserService.java index 4a5c6f5ea0dd1c479216f1f4e7abc3800073393f..dbec05934a87de614f4342280e8207a885569261 100644 --- a/src/main/java/de/thm/arsnova/services/IUserService.java +++ b/src/main/java/de/thm/arsnova/services/IUserService.java @@ -1,9 +1,23 @@ package de.thm.arsnova.services; -import org.springframework.security.core.Authentication; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import de.thm.arsnova.entities.User; +import de.thm.arsnova.exceptions.UnauthorizedException; public interface IUserService { - User getUser(Authentication authentication); + User getCurrentUser() throws UnauthorizedException; + + User getUser2SessionID(UUID sessionID); + void putUser2SessionID(UUID sessionID, User user); + void removeUser2SessionID(UUID sessionID); + Set<Map.Entry<UUID, User>> users2Session(); + + boolean isUserInSession(User user, String keyword); + List<String> getUsersInSession(String keyword); + void addCurrentUserToSessionMap(String keyword); + String getSessionForUser(String username); } diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index 739af73959e6aa041ff51410a4811350b101d1ef..8689bddbfdfb556004a6ba34bb7625df0c22f60e 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -19,19 +19,14 @@ package de.thm.arsnova.services; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Isolation; -import org.springframework.transaction.annotation.Transactional; import de.thm.arsnova.annotation.Authenticated; import de.thm.arsnova.dao.IDatabaseDao; @@ -54,11 +49,12 @@ public class SessionService implements ISessionService { @Value("${feedback.cleanup}") private int cleanupFeedbackDelay; - private static final ConcurrentHashMap<String, String> user2session = new ConcurrentHashMap<String, String>(); - @Autowired IDatabaseDao databaseDao; + @Autowired + IUserService userService; + public void setDatabaseDao(IDatabaseDao databaseDao) { this.databaseDao = databaseDao; } @@ -71,7 +67,8 @@ public class SessionService implements ISessionService { @Override @Authenticated - public Session getSession(String keyword) { + public Session joinSession(String keyword) { + userService.addCurrentUserToSessionMap(keyword); return databaseDao.getSession(keyword); } @@ -113,31 +110,6 @@ public class SessionService implements ISessionService { return databaseDao.sessionKeyAvailable(keyword); } - @Override - public boolean isUserInSession(de.thm.arsnova.entities.User user, String keyword) { - if (keyword == null) return false; - String session = user2session.get(user.getUsername()); - if(session == null) return false; - return keyword.equals(session); - } - - @Override - public List<String> getUsersInSession(String keyword) { - List<String> result = new ArrayList<String>(); - for(Entry<String, String> e : user2session.entrySet()) { - if(e.getValue().equals(keyword)) { - result.add(e.getKey()); - } - } - return result; - } - - @Override - @Transactional(isolation=Isolation.READ_COMMITTED) - public void addUserToSessionMap(String username, String keyword) { - user2session.put(username, keyword); - } - /** * * @param affectedUsers The user whose feedback got deleted along with all affected session keywords @@ -146,7 +118,7 @@ public class SessionService implements ISessionService { public void broadcastFeedbackChanges(Map<String, Set<String>> affectedUsers, Set<String> allAffectedSessions) { for (Map.Entry<String, Set<String>> e : affectedUsers.entrySet()) { // Is this user registered with a socket connection? - String connectedSocket = user2session.get(e.getKey()); + String connectedSocket = userService.getSessionForUser(e.getKey()); if (connectedSocket != null) { this.server.reportDeletedFeedback(e.getKey(), e.getValue()); } diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java index 166c25cbe27fd74ebe39d4b6b3cd0d6b981deeb0..0719a767735bd84e99e5f34d8a5f3d92da866f20 100644 --- a/src/main/java/de/thm/arsnova/services/UserService.java +++ b/src/main/java/de/thm/arsnova/services/UserService.java @@ -1,5 +1,13 @@ package de.thm.arsnova.services; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + import org.scribe.up.profile.facebook.FacebookProfile; import org.scribe.up.profile.google.Google2Profile; import org.scribe.up.profile.twitter.TwitterProfile; @@ -7,42 +15,107 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.cas.authentication.CasAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; import com.github.leleuj.ss.oauth.client.authentication.OAuthAuthenticationToken; import de.thm.arsnova.entities.User; +import de.thm.arsnova.exceptions.UnauthorizedException; public class UserService implements IUserService { + private static final ConcurrentHashMap<UUID, User> socketid2user = new ConcurrentHashMap<UUID, User>(); + private static final ConcurrentHashMap<String, String> user2session = new ConcurrentHashMap<String, String>(); + + @Override - public User getUser(Authentication authentication) { + public User getCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || authentication.getPrincipal() == null) { return null; } + User user = null; + if(authentication instanceof OAuthAuthenticationToken) { OAuthAuthenticationToken token = (OAuthAuthenticationToken) authentication; if(token.getUserProfile() instanceof Google2Profile) { Google2Profile profile = (Google2Profile) token.getUserProfile(); - return new User(profile); + user = new User(profile); } else if(token.getUserProfile() instanceof TwitterProfile) { TwitterProfile profile = (TwitterProfile) token.getUserProfile(); - return new User(profile); + user = new User(profile); } else if(token.getUserProfile() instanceof FacebookProfile) { FacebookProfile profile = (FacebookProfile) token.getUserProfile(); - return new User(profile); + user = new User(profile); } } else if (authentication instanceof CasAuthenticationToken) { CasAuthenticationToken token = (CasAuthenticationToken) authentication; - return new User(token.getAssertion().getPrincipal()); + user = new User(token.getAssertion().getPrincipal()); } else if(authentication instanceof AnonymousAuthenticationToken){ AnonymousAuthenticationToken token = (AnonymousAuthenticationToken) authentication; - return new User(token); + user = new User(token); } else if(authentication instanceof UsernamePasswordAuthenticationToken) { UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; - return new User(token); + user = new User(token); } - return null; + + if (user == null || user.getUsername().equals("anonymous")) throw new UnauthorizedException(); + + return user; + } + + @Override + public User getUser2SessionID(UUID sessionID) { + return socketid2user.get(sessionID); + } + + @Override + public void putUser2SessionID(UUID sessionID, User user) { + socketid2user.put(sessionID, user); + } + + @Override + public Set<Map.Entry<UUID, User>> users2Session() { + return socketid2user.entrySet(); } + @Override + public void removeUser2SessionID(UUID sessionID) { + socketid2user.remove(sessionID); + } + + @Override + public boolean isUserInSession(User user, String keyword) { + if (keyword == null) return false; + String session = user2session.get(user.getUsername()); + if(session == null) return false; + return keyword.equals(session); + } + + @Override + public List<String> getUsersInSession(String keyword) { + List<String> result = new ArrayList<String>(); + for(Entry<String, String> e : user2session.entrySet()) { + if(e.getValue().equals(keyword)) { + result.add(e.getKey()); + } + } + return result; + } + + @Override + @Transactional(isolation=Isolation.READ_COMMITTED) + public void addCurrentUserToSessionMap(String keyword) { + User user = getCurrentUser(); + if (user == null) throw new UnauthorizedException(); + user2session.put(user.getUsername(), keyword); + } + + @Override + public String getSessionForUser(String username) { + return user2session.get(username); + } } diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java index 8298289f93be85448116a3177d30805539ddf37b..8554297f6238c7ffeb309e575b95ce5924fdcb2b 100644 --- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java +++ b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,16 +25,18 @@ import com.corundumstudio.socketio.listener.DisconnectListener; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.User; import de.thm.arsnova.services.ISessionService; +import de.thm.arsnova.services.IUserService; import de.thm.arsnova.socket.message.Feedback; public class ARSnovaSocketIOServer { @Autowired - private ISessionService sessionService; + private ISessionService sessionService; - private final Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private IUserService userService; - private final ConcurrentHashMap<UUID, User> socketid2user = new ConcurrentHashMap<UUID, User>(); + private final Logger logger = LoggerFactory.getLogger(getClass()); private int portNumber; private String hostIp; @@ -75,8 +76,8 @@ public class ARSnovaSocketIOServer { /** * do a check if user is in the session, for which he would give a feedback */ - User u = socketid2user.get(client.getSessionId()); - if(u == null || sessionService.isUserInSession(u, data.getSessionkey()) == false) { + User u = userService.getUser2SessionID(client.getSessionId()); + if(u == null || userService.isUserInSession(u, data.getSessionkey()) == false) { return; } sessionService.saveFeedback(data.getSessionkey(), data.getValue(), u); @@ -86,11 +87,11 @@ public class ARSnovaSocketIOServer { * iterate over all connected clients and if send feedback, * if user is in current session */ - List<String> users = sessionService.getUsersInSession(data.getSessionkey()); + List<String> users = userService.getUsersInSession(data.getSessionkey()); de.thm.arsnova.entities.Feedback fb = sessionService.getFeedback(data.getSessionkey()); for(SocketIOClient c : server.getAllClients()) { - u = socketid2user.get(c.getSessionId()); + u = userService.getUser2SessionID(c.getSessionId()); if(u != null && users.contains(u.getUsername())) { c.sendEvent("updateFeedback", fb.getValues()); } @@ -116,7 +117,7 @@ public class ARSnovaSocketIOServer { @Override public void onDisconnect(SocketIOClient client) { logger.info("addDisconnectListener.onDisconnect: Client: {}", new Object[] {client}); - socketid2user.remove(client.getSessionId()); + userService.removeUser2SessionID(client.getSessionId()); } }); @@ -176,10 +177,6 @@ public class ARSnovaSocketIOServer { this.useSSL = useSSL; } - public void authorize(UUID session, User user) { - socketid2user.put(session, user); - } - public void reportDeletedFeedback(String username, Set<String> arsSessions) { List<UUID> connectionIds = findConnectionIdForUser(username); if (connectionIds.isEmpty()) { @@ -196,7 +193,7 @@ public class ARSnovaSocketIOServer { private List<UUID> findConnectionIdForUser(String username) { List<UUID> result = new ArrayList<UUID>(); - for (Entry<UUID, User> e : socketid2user.entrySet()) { + for (Entry<UUID, User> e : userService.users2Session()) { if (e.getValue().getUsername().equals(username)) { result.add(e.getKey()); } diff --git a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java b/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java index 56418baf032ce1131a97e3e1def117cae3763050..52c7f7725f7fb6e5262bbd2fd57392b5996c7711 100644 --- a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java +++ b/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java @@ -86,6 +86,19 @@ public class SessionControllerTest { assertTrue(response.getStatus() == 401); } + @Test(expected=UnauthorizedException.class) + public void testShouldNotGetSessionIfAnonymous() throws Exception { + userService.setUserAuthenticated(false); + userService.useAnonymousUser(); + + request.setMethod("GET"); + request.setRequestURI("/session/00000000"); + final ModelAndView mav = handlerAdapter.handle(request, response, sessionController); + + assertNull(mav); + assertTrue(response.getStatus() == 401); + } + @Test(expected=UnauthorizedException.class) public void testShouldCreateSessionIfUnauthorized() throws Exception { userService.setUserAuthenticated(false); diff --git a/src/test/java/de/thm/arsnova/services/StubUserService.java b/src/test/java/de/thm/arsnova/services/StubUserService.java index 8ab9f5712167ce10d7d1580a68e9a9aa9e8c437a..53bbd15700fbcce37c5a0ba2100bf38f94fc81ef 100644 --- a/src/test/java/de/thm/arsnova/services/StubUserService.java +++ b/src/test/java/de/thm/arsnova/services/StubUserService.java @@ -1,10 +1,13 @@ package de.thm.arsnova.services; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; import de.thm.arsnova.entities.User; -import de.thm.arsnova.services.IUserService; public class StubUserService implements IUserService { @@ -18,8 +21,60 @@ public class StubUserService implements IUserService { stubUser = null; } + public void useAnonymousUser() { + stubUser = new User(new UsernamePasswordAuthenticationToken("anonymous","")); + } + @Override - public User getUser(Authentication authentication) { + public User getCurrentUser() { return stubUser; } + + @Override + public User getUser2SessionID(UUID sessionID) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void putUser2SessionID(UUID sessionID, User user) { + // TODO Auto-generated method stub + + } + + @Override + public void removeUser2SessionID(UUID sessionID) { + // TODO Auto-generated method stub + + } + + @Override + public Set<Entry<UUID, User>> users2Session() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isUserInSession(User user, String keyword) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List<String> getUsersInSession(String keyword) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getSessionForUser(String username) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addCurrentUserToSessionMap(String keyword) { + // TODO Auto-generated method stub + + } }