From 596a007bee818c41ce0e40ab239a0b57caa1899a Mon Sep 17 00:00:00 2001
From: Daniel Gerhardt <code@dgerhardt.net>
Date: Sat, 10 Feb 2018 17:36:21 +0100
Subject: [PATCH] Use aspect to populate SecurityContextHolder from WebSocket
 listeners

---
 .../java/de/thm/arsnova/security/User.java    |  9 ++
 .../websocket/ArsnovaSocketioServerImpl.java  | 19 +----
 .../WebsocketAuthenticationAspect.java        | 85 +++++++++++++++++++
 src/main/resources/META-INF/aop.xml           |  3 +-
 4 files changed, 97 insertions(+), 19 deletions(-)
 create mode 100644 src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java

diff --git a/src/main/java/de/thm/arsnova/security/User.java b/src/main/java/de/thm/arsnova/security/User.java
index 155074d10..a6dad6e6b 100644
--- a/src/main/java/de/thm/arsnova/security/User.java
+++ b/src/main/java/de/thm/arsnova/security/User.java
@@ -17,6 +17,7 @@
  */
 package de.thm.arsnova.security;
 
+import de.thm.arsnova.entities.UserAuthentication;
 import de.thm.arsnova.entities.UserProfile;
 import org.springframework.security.core.GrantedAuthority;
 
@@ -56,6 +57,14 @@ public class User implements org.springframework.security.core.userdetails.UserD
 		providerUserDetails = details;
 	}
 
+	public User(final UserAuthentication userAuthentication, final Collection<? extends GrantedAuthority> authorities) {
+		id = userAuthentication.getId();
+		loginId = userAuthentication.getUsername();
+		authProvider = userAuthentication.getAuthProvider();
+		this.authorities = authorities;
+		enabled = true;
+	}
+
 	@Override
 	public Collection<? extends GrantedAuthority> getAuthorities() {
 		return authorities;
diff --git a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
index 1baf2d054..671cc61eb 100644
--- a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
+++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
@@ -48,11 +48,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Required;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.AuthorityUtils;
-import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PreDestroy;
@@ -96,7 +91,6 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova
 	private String storepass;
 	private final Configuration config;
 	private SocketIOServer server;
-	private boolean securityInitialized;
 
 	public ArsnovaSocketioServerImpl() {
 		config = new Configuration();
@@ -244,9 +238,7 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova
 			@Override
 			@Timed
 			public void onConnect(final SocketIOClient client) {
-				if (!securityInitialized) {
-					initializeSecurity();
-				}
+
 			}
 		});
 
@@ -689,13 +681,4 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova
 
 	@Override
 	public void visit(DeleteRoomEvent event) { }
-
-	private void initializeSecurity() {
-		Authentication auth = new AnonymousAuthenticationToken("websocket", "websocket",
-				AuthorityUtils.createAuthorityList("ROLE_WEBSOCKET_ACCESS"));
-		SecurityContext context = SecurityContextHolder.createEmptyContext();
-		context.setAuthentication(auth);
-		SecurityContextHolder.setContext(context);
-		securityInitialized = true;
-	}
 }
diff --git a/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java b/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java
new file mode 100644
index 000000000..6f86f6056
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java
@@ -0,0 +1,85 @@
+/*
+ * This file is part of ARSnova Backend.
+ * Copyright (C) 2012-2018 The ARSnova Team
+ *
+ * ARSnova Backend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ARSnova Backend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package de.thm.arsnova.websocket;
+
+import com.corundumstudio.socketio.SocketIOClient;
+import de.thm.arsnova.entities.UserAuthentication;
+import de.thm.arsnova.security.User;
+import de.thm.arsnova.services.UserService;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * This aspect populates the SecurityContextHolder of Spring Security when data are received via WebSockets.
+ * It allows WebSocket listeners to access service methods which are secured by Spring Security annotations.
+ *
+ * @author Daniel Gerhardt
+ */
+@Aspect
+@Configurable
+public class WebsocketAuthenticationAspect {
+	private static final Logger logger = LoggerFactory.getLogger(WebsocketAuthenticationAspect.class);
+	private static final GrantedAuthority WEBSOCKET_AUTHORITY = new SimpleGrantedAuthority("ROLE_WEBSOCKET_ACCESS");
+
+	private UserService userService;
+
+	@Around("execution(void com.corundumstudio.socketio.listener.DataListener+.onData(..)) && args(client, message, ..)")
+	public <T> void handleWebsocketAuthentication(final ProceedingJoinPoint pjp,
+			final SocketIOClient client, final T message) throws Throwable {
+		logger.debug("Executing WebsocketAuthenticationAspect for onData event: Session Id: {}, Message Class: {}",
+				client.getSessionId(), message.getClass());
+		populateSecurityContext(client.getSessionId());
+		pjp.proceed();
+		clearSecurityContext();
+	}
+
+	private void populateSecurityContext(final UUID socketId) {
+		SecurityContext context = SecurityContextHolder.getContext();
+		UserAuthentication userAuth = userService.getUserToSocketId(socketId);
+		Set<GrantedAuthority> authorities = new HashSet<>();
+		authorities.add(WEBSOCKET_AUTHORITY);
+		User user = new User(userAuth, authorities);
+		Authentication auth = new UsernamePasswordAuthenticationToken(user, null, authorities);
+		context.setAuthentication(auth);
+		SecurityContextHolder.setContext(context);
+	}
+
+	private void clearSecurityContext() {
+		SecurityContextHolder.clearContext();
+	}
+
+	@Autowired
+	public void setUserService(final UserService userService) {
+		this.userService = userService;
+	}
+}
diff --git a/src/main/resources/META-INF/aop.xml b/src/main/resources/META-INF/aop.xml
index 3b810cd0d..d897ff7f6 100644
--- a/src/main/resources/META-INF/aop.xml
+++ b/src/main/resources/META-INF/aop.xml
@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <aspectj>
 	<weaver options="-verbose -showWeaveInfo">
-		<!-- only weave classes in our application-specific packages -->
 		<include within="de.thm.arsnova..*"/>
+		<include within="com.corundumstudio.socketio.listener.*"/>
 	</weaver>
 
 	<aspects>
 		<aspect name="de.thm.arsnova.aop.RangeAspect"/>
+		<aspect name="de.thm.arsnova.websocket.WebsocketAuthenticationAspect"/>
 	</aspects>
 </aspectj>
-- 
GitLab