package org.argeo.cms.websocket.server; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig.Configurator; import org.argeo.api.cms.CmsAuth; import org.argeo.api.cms.CmsLog; import org.argeo.cms.auth.RemoteAuthCallbackHandler; import org.argeo.cms.auth.RemoteAuthRequest; import org.argeo.cms.auth.RemoteAuthResponse; import org.argeo.cms.auth.RemoteAuthSession; import org.argeo.cms.auth.RemoteAuthUtils; import org.argeo.cms.servlet.CmsServletContext; /** * Disabled until third party issues are solved.. Customises * the initialisation of a new web socket. */ public class CmsWebSocketConfigurator extends Configurator { public final static String WEBSOCKET_SUBJECT = "org.argeo.cms.websocket.subject"; public final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user"; private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class); final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; @Override public boolean checkOrigin(String originHeaderValue) { return true; } @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { try { return endpointClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new IllegalArgumentException("Cannot get endpoint instance", e); } } @Override public List getNegotiatedExtensions(List installed, List requested) { return requested; } @Override public String getNegotiatedSubprotocol(List supported, List requested) { if ((requested == null) || (requested.size() == 0)) return ""; if ((supported == null) || (supported.isEmpty())) return ""; for (String possible : requested) { if (possible == null) continue; if (supported.contains(possible)) return possible; } return ""; } @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { // if (true) // return; WebSocketHandshakeRequest remoteAuthRequest = new WebSocketHandshakeRequest(request); WebSocketHandshakeResponse remoteAuthResponse = new WebSocketHandshakeResponse(response); // RemoteAuthSession httpSession = new ServletHttpSession( // (javax.servlet.http.HttpSession) request.getHttpSession()); RemoteAuthSession remoteAuthSession = remoteAuthRequest.getSession(); if (log.isDebugEnabled() && remoteAuthSession != null) log.debug("Web socket HTTP session id: " + remoteAuthSession.getId()); // if (remoteAuthSession == null) { // rejectResponse(response, null); // } ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); LoginContext lc; try { lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse)); lc.login(); } catch (LoginException e) { // FIXME better analyse failure so as not to try endlessly if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) { int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthResponse, "Argeo", true); remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode)); return; } else { lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse); } if (lc == null) rejectResponse(response, e); } finally { Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); } Subject subject = lc.getSubject(); Subject.doAs(subject, new PrivilegedAction() { @Override public Void run() { // TODO also set login context in order to log out ? RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest); return null; } }); } protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { return true; } /** * Behaviour when the web socket could not be authenticated. Throws an * {@link IllegalStateException} by default. * * @param e can be null */ protected void rejectResponse(HandshakeResponse response, Exception e) { response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList()); // violent implementation, as suggested in // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake // throw new IllegalStateException("Web socket cannot be authenticated"); } }