Improve remote authentication
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 27 Jul 2022 07:51:57 +0000 (09:51 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 27 Jul 2022 07:51:57 +0000 (09:51 +0200)
16 files changed:
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java
org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java
org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg
org.argeo.util/src/org/argeo/util/http/HttpHeader.java
org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java [new file with mode: 0644]

index cec04d230714edda51cefe4fcc0f575fdfd14138..09f17ae02cea7274d0fc5e116505f0c6ee250934 100644 (file)
@@ -2,10 +2,8 @@ package org.argeo.cms.integration;
 
 import java.io.IOException;
 import java.security.AccessControlContext;
-import java.security.PrivilegedAction;
 import java.util.Map;
 
-import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletRequest;
@@ -45,31 +43,31 @@ public class CmsPrivateServletContext extends ServletContextHelper {
                if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
                        return true;
                try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response));
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(request, response));
                        lc.login();
                } catch (LoginException e) {
                        lc = processUnauthorized(req, resp);
                        if (lc == null)
                                return false;
                }
-               Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(request);
-                               return null;
-                       }
-
-               });
+//             Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(request);
+//                             return null;
+//                     }
+//
+//             });
 
                return true;
        }
 
-       @Override
-       public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
-               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
-       }
+//     @Override
+//     public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
+//             RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
+//     }
 
        protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
                try {
index dd6467216f3340c67e2a3c20a2d7cc9525aacd7a..6a5208730fdd4af179afbc1de536367629365b28 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.cms.servlet;
 
 import java.io.IOException;
 import java.net.URL;
+import java.net.http.HttpHeaders;
 import java.security.PrivilegedAction;
 import java.util.Map;
 
@@ -18,6 +19,7 @@ import org.argeo.cms.auth.RemoteAuthRequest;
 import org.argeo.cms.auth.RemoteAuthResponse;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.internal.HttpUtils;
+import org.argeo.util.http.HttpHeader;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.service.http.context.ServletContextHelper;
@@ -55,9 +57,10 @@ public class CmsServletContext extends ServletContextHelper {
                        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, httpAuthRealm, forceBasic);
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest,
+                                               remoteAuthResponse, httpAuthRealm,
+                                               forceBasic);
                                response.setStatus(statusCode);
                                return false;
 
@@ -70,24 +73,24 @@ public class CmsServletContext extends ServletContextHelper {
                        Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
                }
 
-               Subject subject = lc.getSubject();
-               Subject.doAs(subject, new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
-                               return null;
-                       }
-
-               });
+//             Subject subject = lc.getSubject();
+//             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+//                             return null;
+//                     }
+//
+//             });
                return true;
        }
 
-       @Override
-       public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
-               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
-       }
+//     @Override
+//     public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
+//             RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+//     }
 
        protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
                return false;
index 0ded176f46ffef6a291a13cedc342cc01653a80e..4dfdc5d2154ae524d644ae94f790b3673018f8dd 100644 (file)
@@ -1,10 +1,8 @@
 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;
@@ -27,11 +25,10 @@ import org.argeo.cms.servlet.CmsServletContext;
  * 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";
+
+       private final String httpAuthRealm = "Argeo";
 
        @Override
        public boolean checkOrigin(String originHeaderValue) {
@@ -90,10 +87,10 @@ public class CmsWebSocketConfigurator extends Configurator {
                        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));
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, remoteAuthResponse, httpAuthRealm,
+                                               true);
+//                             remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode));
                                return;
                        } else {
                                lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
@@ -106,17 +103,17 @@ public class CmsWebSocketConfigurator extends Configurator {
                        Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
                }
 
-               Subject subject = lc.getSubject();
-               Subject.doAs(subject, new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
-                               return null;
-                       }
-
-               });
+//             Subject subject = lc.getSubject();
+//             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                     @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) {
index 396af9343f48d74a3933c503b7437d6003c959ba..d7d6c282ca7fb24ee4a58e2eddcc9aa513146912 100644 (file)
@@ -20,6 +20,7 @@ import javax.security.auth.x500.X500Principal;
 
 import org.argeo.api.cms.AnonymousPrincipal;
 import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.DataAdminPrincipal;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
@@ -144,7 +145,7 @@ class CmsAuthUtils {
                        CmsSessionImpl cmsSession;
                        CmsSessionImpl currentLocalSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessId);
                        if (currentLocalSession != null) {
-                               boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null;
+                               boolean currentLocalSessionAnonymous = currentLocalSession.isAnonymous();
                                if (!anonymous) {
                                        if (currentLocalSessionAnonymous) {
                                                currentLocalSession.close();
@@ -191,6 +192,7 @@ class CmsAuthUtils {
                                        throw new IllegalStateException(
                                                        "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
                        }
+                       request.setAttribute(CmsSession.class.getName(), cmsSession);
                } else {
                        CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(SINGLE_USER_LOCAL_ID);
                        if (cmsSession == null) {
index 757bf73bfc43513fab8c004aaa3eb363a005f22a..af274d316802603717ee94df432f2fff1181795b 100644 (file)
@@ -1,7 +1,5 @@
 package org.argeo.cms.auth;
 
-import java.security.AccessControlContext;
-import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Base64;
 import java.util.function.Supplier;
@@ -15,7 +13,9 @@ import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.util.CurrentSubject;
 import org.argeo.util.http.HttpHeader;
+import org.argeo.util.http.HttpResponseStatus;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
@@ -44,47 +44,49 @@ public class RemoteAuthUtils {
         * Useful to log in to JCR.
         */
        public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
-               ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
-               try {
-                       return Subject.doAs(
-                                       Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
-                                       new PrivilegedAction<T>() {
-
-                                               @Override
-                                               public T run() {
-                                                       return supplier.get();
-                                               }
-
-                                       });
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextCl);
-               }
+               CmsSession cmsSession = getCmsSession(req);
+               return CurrentSubject.callAs(cmsSession.getSubject(), () -> supplier.get());
+//             ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+//             Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+//             try {
+//                     return Subject.doAs(
+//                                     Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
+//                                     new PrivilegedAction<T>() {
+//
+//                                             @Override
+//                                             public T run() {
+//                                                     return supplier.get();
+//                                             }
+//
+//                                     });
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentContextCl);
+//             }
        }
 
-       public final static void configureRequestSecurity(RemoteAuthRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) != null)
-                       throw new IllegalStateException("Request already authenticated.");
-               AccessControlContext acc = AccessController.getContext();
-               req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
-               req.setAttribute(AccessControlContext.class.getName(), acc);
-       }
-
-       public final static void clearRequestSecurity(RemoteAuthRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) == null)
-                       throw new IllegalStateException("Cannot clear non-authenticated request.");
-               req.setAttribute(REMOTE_USER, null);
-               req.setAttribute(AccessControlContext.class.getName(), null);
-       }
+//     public final static void configureRequestSecurity(RemoteAuthRequest req) {
+//             if (req.getAttribute(AccessControlContext.class.getName()) != null)
+//                     throw new IllegalStateException("Request already authenticated.");
+//             AccessControlContext acc = AccessController.getContext();
+//             req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
+//             req.setAttribute(AccessControlContext.class.getName(), acc);
+//     }
+//
+//     public final static void clearRequestSecurity(RemoteAuthRequest req) {
+//             if (req.getAttribute(AccessControlContext.class.getName()) == null)
+//                     throw new IllegalStateException("Cannot clear non-authenticated request.");
+//             req.setAttribute(REMOTE_USER, null);
+//             req.setAttribute(AccessControlContext.class.getName(), null);
+//     }
 
        public static CmsSession getCmsSession(RemoteAuthRequest req) {
-               Subject subject = Subject
-                               .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
-               CmsSession cmsSession = CmsContextImpl.getCmsContext().getCmsSession(subject);
+               CmsSession cmsSession = (CmsSession) req.getAttribute(CmsSession.class.getName());
+               if (cmsSession == null)
+                       throw new IllegalStateException("Request must have a CMS session attribute");
                return cmsSession;
        }
 
-       public static String getGssToken(Subject subject, String service, String server) {
+       public static String createGssToken(Subject subject, String service, String server) {
                if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty())
                        throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated.");
                return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
@@ -144,13 +146,25 @@ public class RemoteAuthUtils {
                }
        }
 
-       public static int askForWwwAuth(RemoteAuthResponse remoteAuthResponse, String realm, boolean forceBasic) {
+       public static int askForWwwAuth(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse,
+                       String realm, boolean forceBasic) {
+               boolean negotiateFailed = false;
+               if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) != null) {
+                       // we already tried, so we give up in order not too loop endlessly
+                       if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName())
+                                       .startsWith(HttpHeader.NEGOTIATE)) {
+                               negotiateFailed = true;
+                       } else {
+                               return HttpResponseStatus.FORBIDDEN.getStatusCode();
+                       }
+               }
+
                // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
                // realm=\"" + httpAuthRealm + "\"");
-               if (hasAcceptorCredentials() && !forceBasic)// SPNEGO
-                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
+               if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed)// SPNEGO
+                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE);
                else
-                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
                                        HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
 
                // response.setDateHeader("Date", System.currentTimeMillis());
@@ -161,7 +175,7 @@ public class RemoteAuthUtils {
                // response.setHeader("Keep-Alive", "timeout=5, max=97");
                // response.setContentType("text/html; charset=UTF-8");
 
-               return 401;
+               return HttpResponseStatus.UNAUTHORIZED.getStatusCode();
        }
 
        private static boolean hasAcceptorCredentials() {
index 6bf3fc985a8d1040edfbab0692a4a4e8cd991dae..d2ceb27a509860ece72d1c36136c70fb053fcb38 100644 (file)
@@ -94,6 +94,8 @@ public class RemoteSessionLoginModule implements LoginModule {
                                                if (log.isTraceEnabled())
                                                        log.trace("Retrieved authorization from " + cmsSession);
                                        }
+                               }else {
+                                       request.createSession();
                                }
                        }
                        sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
@@ -116,7 +118,7 @@ public class RemoteSessionLoginModule implements LoginModule {
        public boolean commit() throws LoginException {
                byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
                if (outToken != null) {
-                       response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+                       response.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
                                        "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
                }
 
@@ -154,7 +156,7 @@ public class RemoteSessionLoginModule implements LoginModule {
        }
 
        private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
-               String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getName());
+               String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName());
                extractHttpAuth(authHeader);
        }
 
index e24e5b45b6a1f456c36753daf1e2c377c210bdf3..a01daf6e0adef7228725ef66833a99d62d66aabb 100644 (file)
@@ -12,6 +12,7 @@ import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
 
 import com.sun.security.jgss.GSSUtil;
 
@@ -41,32 +42,20 @@ public class SpnegoLoginModule implements LoginModule {
                if (gssContext == null)
                        return false;
                else {
-//                     if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
-//                             try {
-//                                     GSSName name = gssContext.getSrcName();
-//                                     String username = name.toString();
-//                                     // TODO deal with connecting service
-//                                     // TODO generate IPA DN?
-//                                     username = username.substring(0, username.lastIndexOf('@'));
-//                                     sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
-//                             } catch (GSSException e) {
-//                                     throw new IllegalStateException("Cannot retrieve SPNEGO name", e);
-//                             }
-//                     }
+                       if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+                               try {
+                                       if (gssContext.getCredDelegState()) {
+                                               // commit will succeeed only if we have credential delegation
+                                               GSSName name = gssContext.getSrcName();
+                                               String username = name.toString();
+                                               sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+                                       }
+                               } catch (GSSException e) {
+                                       throw new IllegalStateException("Cannot retrieve SPNEGO name", e);
+                               }
+                       }
                        return true;
                }
-               // try {
-               // String clientName = gssContext.getSrcName().toString();
-               // String role = clientName.substring(clientName.indexOf('@') + 1);
-               //
-               // log.debug("SpnegoUserRealm: established a security context");
-               // log.debug("Client Principal is: " + gssContext.getSrcName());
-               // log.debug("Server Principal is: " + gssContext.getTargName());
-               // log.debug("Client Default Role: " + role);
-               // } catch (GSSException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
        }
 
        @Override
@@ -75,13 +64,12 @@ public class SpnegoLoginModule implements LoginModule {
                        return false;
 
                try {
-//                     Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
-//                     Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
                        Subject gssSubject;
                        if (gssContext.getCredDelegState())
                                gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), gssContext.getDelegCred());
                        else
                                gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), null);
+                       // without credential delegation we won't have access to the Kerberos key
                        subject.getPrincipals().addAll(gssSubject.getPrincipals());
                        subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials());
                        return true;
@@ -123,7 +111,6 @@ public class SpnegoLoginModule implements LoginModule {
                GSSManager manager = GSSManager.getInstance();
                try {
                        GSSContext gContext = manager.createContext(CmsContextImpl.getCmsContext().getAcceptorCredentials());
-
                        if (gContext == null) {
                                log.debug("SpnegoUserRealm: failed to establish GSSContext");
                        } else {
index 0d8f8d62933847ee7e8e59189db0d75330ef22a6..4c9d094802026535e7525e543eab1494af113f9c 100644 (file)
@@ -90,17 +90,13 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
                        // // TODO locale?
+               } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
+                               && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN)) {
+                       // SPNEGO login has succeeded, that's enough for us at this stage
+                       return true;
                } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
                                && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN)) {
                        String certDn = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
-//                     LdapName ldapName;
-//                     try {
-//                             ldapName = new LdapName(certificateName);
-//                     } catch (InvalidNameException e) {
-//                             e.printStackTrace();
-//                             return false;
-//                     }
-//                     username = ldapName.getRdn(ldapName.size() - 1).getValue().toString();
                        username = certDn;
                        certificateChain = sharedState.get(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN);
                        password = null;
@@ -110,11 +106,6 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = null;
                        preauth = true;
-//             } else if (singleUser) {
-//                     username = OsUserUtils.getOsUsername();
-//                     password = null;
-//                     // TODO retrieve from http session
-//                     locale = Locale.getDefault();
                } else {
 
                        // ask for username and password
index 42313dd4198cbaca331f68e768c49f8ce0984e1b..e5beb69da84371be0acb4656bc9f3a8608c65b55 100644 (file)
@@ -40,10 +40,10 @@ public class SpnegoHttpClient {
                        lc.login();
 
                        HttpClient httpClient = openHttpClient(lc.getSubject());
-                       String token = RemoteAuthUtils.getGssToken(lc.getSubject(), "HTTP", server);
+                       String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", server);
 
                        HttpRequest request = HttpRequest.newBuilder().uri(u.toURI()) //
-                                       .header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token) //
+                                       .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token) //
                                        .build();
                        BodyHandler<String> bodyHandler = BodyHandlers.ofString();
                        HttpResponse<String> response = httpClient.send(request, bodyHandler);
index 5834bd611da137cb4192cd76b8d7572f7eb7b8b6..7a80c780662dff097fb1486532509428c4c92889 100644 (file)
@@ -44,11 +44,11 @@ public class WebSocketEventClient {
                System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
                LoginContext lc = new LoginContext("SINGLE_USER");
                lc.login();
-               String token = RemoteAuthUtils.getGssToken(lc.getSubject(), "HTTP", uri.getHost());
+               String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost());
 
                HttpClient client = HttpClient.newHttpClient();
                CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
-                               .header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token)
+                               .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token)
                                .buildAsync(uri, listener);
 
                WebSocket webSocket = ws.get();
index e78567b07df03ebcbfd44d286f5f8c424ea36615..dfedfab1968f02a474bc073f3c7f15914ee810c2 100644 (file)
@@ -1,9 +1,6 @@
 package org.argeo.cms.internal.auth;
 
 import java.io.Serializable;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -12,11 +9,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.UUID;
 import java.util.function.Consumer;
 
-import javax.crypto.SecretKey;
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
@@ -27,17 +22,14 @@ import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
-import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.useradmin.Authorization;
 
 /** Default CMS session implementation. */
 public class CmsSessionImpl implements CmsSession, Serializable {
        private static final long serialVersionUID = 1867719354246307225L;
-//     private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
        private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class);
 
-       // private final Subject initialSubject;
-       private transient AccessControlContext accessControlContext;
+       private transient Subject subject;
        private final UUID uuid;
        private final String localSessionId;
        private Authorization authorization;
@@ -49,8 +41,6 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        private ZonedDateTime end;
        private final Locale locale;
 
-       private ServiceRegistration<CmsSession> serviceRegistration;
-
        private Map<String, Object> views = new HashMap<>();
 
        private List<Consumer<CmsSession>> onCloseCallbacks = Collections.synchronizedList(new ArrayList<>());
@@ -61,15 +51,7 @@ public class CmsSessionImpl implements CmsSession, Serializable {
 
                this.creationTime = ZonedDateTime.now();
                this.locale = locale;
-               this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
-
-                       @Override
-                       public AccessControlContext run() {
-                               return AccessController.getContext();
-                       }
-
-               });
-               // this.initialSubject = initialSubject;
+               this.subject = initialSubject;
                this.localSessionId = localSessionId;
                this.authorization = authorization;
                if (authorization.getName() != null) {
@@ -102,7 +84,7 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                } catch (LoginException e) {
                        log.warn("Could not logout " + getSubject() + ": " + e);
                } finally {
-                       accessControlContext = null;
+                       subject = null;
                }
                log.debug("Closed " + this);
        }
@@ -113,7 +95,7 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        }
 
        public Subject getSubject() {
-               return Subject.getSubject(accessControlContext);
+               return subject;
        }
 
 //     public Set<SecretKey> getSecretKeys() {
index 307f928a5c9658f52bca202cf8d24f0655c0e403..164e9b9b2b24245f913773796d661fc362409a63 100644 (file)
@@ -10,7 +10,6 @@ import org.argeo.cms.auth.RemoteAuthCallbackHandler;
 import org.argeo.cms.auth.RemoteAuthRequest;
 import org.argeo.cms.auth.RemoteAuthResponse;
 import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.util.CurrentSubject;
 
 import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.HttpExchange;
@@ -38,9 +37,9 @@ public class CmsAuthenticator extends Authenticator {
                        lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
                        lc.login();
                } catch (LoginException e) {
-                       // FIXME better analyse failure so as not to try endlessly
-                       if (authIsRequired(remoteAuthExchange,remoteAuthExchange)) {
-                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, httpAuthRealm, forceBasic);
+                       if (authIsRequired(remoteAuthExchange, remoteAuthExchange)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, remoteAuthExchange, httpAuthRealm,
+                                               forceBasic);
                                return new Authenticator.Retry(statusCode);
 
                        } else {
@@ -54,10 +53,10 @@ public class CmsAuthenticator extends Authenticator {
 
                Subject subject = lc.getSubject();
 
-               CurrentSubject.callAs(subject, () -> {
-                       RemoteAuthUtils.configureRequestSecurity(remoteAuthExchange);
-                       return null;
-               });
+//             CurrentSubject.callAs(subject, () -> {
+//                     RemoteAuthUtils.configureRequestSecurity(remoteAuthExchange);
+//                     return null;
+//             });
 //             Subject.doAs(subject, new PrivilegedAction<Void>() {
 //
 //                     @Override
@@ -73,8 +72,7 @@ public class CmsAuthenticator extends Authenticator {
                return new Authenticator.Success(httpPrincipal);
        }
 
-       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest,
-                       RemoteAuthResponse remoteAuthResponse) {
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
                return true;
        }
 
index 643e53d90db868b38d3d76e9ff076a6e49d6ec9a..51db582c69c091bdd7aa79036ff21005b0a9c7e8 100644 (file)
@@ -1,8 +1,10 @@
 USER {
     org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
     org.argeo.cms.auth.SpnegoLoginModule optional;
-    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
-    org.argeo.cms.auth.UserAdminLoginModule sufficient;
+    com.sun.security.auth.module.Krb5LoginModule optional
+     tryFirstPass=true
+     storeKey=true;
+    org.argeo.cms.auth.UserAdminLoginModule required;
 };
 
 ANONYMOUS {
index 364977d4b3b9054cac876bb96a05007f8739ba9d..8290fb339807838d878ac9d181b967b7fa711ba9 100644 (file)
@@ -1,7 +1,7 @@
 USER {
     org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
     org.argeo.cms.auth.IdentLoginModule optional;
-    org.argeo.cms.auth.UserAdminLoginModule requisite;
+    org.argeo.cms.auth.UserAdminLoginModule required;
 };
 
 ANONYMOUS {
index a6d4186c3c290abe1f6525c255d82fde20365dbd..2fb8f302c68fb2deb15567bfd3742e96746404b5 100644 (file)
@@ -16,13 +16,13 @@ public enum HttpHeader {
                this.name = headerName;
        }
 
-       public String getName() {
+       public String getHeaderName() {
                return name;
        }
 
        @Override
        public String toString() {
-               return getName();
+               return getHeaderName();
        }
 
 }
diff --git a/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java b/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java
new file mode 100644 (file)
index 0000000..ec67cce
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.util.http;
+
+/**
+ * Standard HTTP response status codes.
+ * 
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+ */
+public enum HttpResponseStatus {
+       UNAUTHORIZED(401), //
+       FORBIDDEN(403), //
+       NOT_FOUND(404),//
+       ;
+
+       private final int statusCode;
+
+       HttpResponseStatus(int statusCode) {
+               this.statusCode = statusCode;
+       }
+
+       public int getStatusCode() {
+               return statusCode;
+       }
+
+}