View Javadoc
1   package org.argeo.cms.websocket;
2   
3   import java.security.AccessController;
4   import java.security.PrivilegedAction;
5   import java.util.List;
6   
7   import javax.security.auth.Subject;
8   import javax.security.auth.login.LoginContext;
9   import javax.servlet.http.HttpSession;
10  import javax.websocket.Extension;
11  import javax.websocket.HandshakeResponse;
12  import javax.websocket.server.HandshakeRequest;
13  import javax.websocket.server.ServerEndpointConfig;
14  import javax.websocket.server.ServerEndpointConfig.Configurator;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  import org.argeo.api.NodeConstants;
19  import org.argeo.cms.auth.HttpRequestCallbackHandler;
20  import org.osgi.service.http.context.ServletContextHelper;
21  
22  /** Customises the initialisation of a new web socket. */
23  public class CmsWebSocketConfigurator extends Configurator {
24  	public final static String WEBSOCKET_SUBJECT = "org.argeo.cms.websocket.subject";
25  
26  	private final static Log log = LogFactory.getLog(CmsWebSocketConfigurator.class);
27  	final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
28  
29  	@Override
30  	public boolean checkOrigin(String originHeaderValue) {
31  		return true;
32  	}
33  
34  	@Override
35  	public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
36  		try {
37  			return endpointClass.getDeclaredConstructor().newInstance();
38  		} catch (Exception e) {
39  			throw new IllegalArgumentException("Cannot get endpoint instance", e);
40  		}
41  	}
42  
43  	@Override
44  	public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
45  		return requested;
46  	}
47  
48  	@Override
49  	public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
50  		if ((requested == null) || (requested.size() == 0))
51  			return "";
52  		if ((supported == null) || (supported.isEmpty()))
53  			return "";
54  		for (String possible : requested) {
55  			if (possible == null)
56  				continue;
57  			if (supported.contains(possible))
58  				return possible;
59  		}
60  		return "";
61  	}
62  
63  	@Override
64  	public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
65  		HttpSession httpSession = (HttpSession) request.getHttpSession();
66  		if (log.isDebugEnabled() && httpSession != null)
67  			log.debug("Web socket HTTP session id: " + httpSession.getId());
68  
69  		if (httpSession == null) {
70  			rejectResponse(response, null);
71  		}
72  		try {
73  			LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
74  					new HttpRequestCallbackHandler(httpSession));
75  			lc.login();
76  			if (log.isDebugEnabled())
77  				log.debug("Web socket logged-in as " + lc.getSubject());
78  			Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
79  
80  				@Override
81  				public Void run() {
82  					sec.getUserProperties().put(ServletContextHelper.REMOTE_USER, AccessController.getContext());
83  					return null;
84  				}
85  
86  			});
87  		} catch (Exception e) {
88  			rejectResponse(response, e);
89  		}
90  	}
91  
92  	/**
93  	 * Behaviour when the web socket could not be authenticated. Throws an
94  	 * {@link IllegalStateException} by default.
95  	 * 
96  	 * @param e can be null
97  	 */
98  	protected void rejectResponse(HandshakeResponse response, Exception e) {
99  		// violent implementation, as suggested in
100 		// https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
101 //		throw new IllegalStateException("Web socket cannot be authenticated");
102 	}
103 }