From 6254373e6005cf77f218ab5b8c54fdc72bb97ca4 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 27 Jul 2022 09:51:57 +0200 Subject: [PATCH] Improve remote authentication --- .../integration/CmsPrivateServletContext.java | 32 +++---- .../argeo/cms/servlet/CmsServletContext.java | 37 +++---- .../server/CmsWebSocketConfigurator.java | 35 ++++--- .../src/org/argeo/cms/auth/CmsAuthUtils.java | 4 +- .../org/argeo/cms/auth/RemoteAuthUtils.java | 96 +++++++++++-------- .../cms/auth/RemoteSessionLoginModule.java | 6 +- .../org/argeo/cms/auth/SpnegoLoginModule.java | 41 +++----- .../argeo/cms/auth/UserAdminLoginModule.java | 17 +--- .../argeo/cms/client/SpnegoHttpClient.java | 4 +- .../cms/client/WebSocketEventClient.java | 4 +- .../cms/internal/auth/CmsSessionImpl.java | 26 +---- .../cms/internal/http/CmsAuthenticator.java | 18 ++-- .../argeo/cms/internal/runtime/jaas-ipa.cfg | 6 +- .../org/argeo/cms/internal/runtime/jaas.cfg | 2 +- .../src/org/argeo/util/http/HttpHeader.java | 4 +- .../argeo/util/http/HttpResponseStatus.java | 24 +++++ 16 files changed, 178 insertions(+), 178 deletions(-) create mode 100644 org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java index cec04d230..09f17ae02 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java @@ -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() { - - @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() { +// +// @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 { diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java index dd6467216..6a5208730 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java @@ -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() { - - @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() { +// +// @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; diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java index 0ded176f4..4dfdc5d21 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java @@ -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() { - - @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() { +// +// @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) { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index 396af9343..d7d6c282c 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -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) { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index 757bf73bf..af274d316 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -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 doAs(Supplier 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() { - - @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() { +// +// @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) () -> { @@ -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() { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index 6bf3fc985..d2ceb27a5 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -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); } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index e24e5b45b..a01daf6e0 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -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 { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 0d8f8d629..4c9d09480 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -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 diff --git a/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java b/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java index 42313dd41..e5beb69da 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java @@ -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 bodyHandler = BodyHandlers.ofString(); HttpResponse response = httpClient.send(request, bodyHandler); diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java index 5834bd611..7a80c7806 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java @@ -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 ws = client.newWebSocketBuilder() - .header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token) + .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token) .buildAsync(uri, listener); WebSocket webSocket = ws.get(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java index e78567b07..dfedfab19 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -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 serviceRegistration; - private Map views = new HashMap<>(); private List> 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() { - - @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 getSecretKeys() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java index 307f928a5..164e9b9b2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java @@ -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() { // // @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; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg index 643e53d90..51db582c6 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg @@ -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 { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg index 364977d4b..8290fb339 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg @@ -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 { diff --git a/org.argeo.util/src/org/argeo/util/http/HttpHeader.java b/org.argeo.util/src/org/argeo/util/http/HttpHeader.java index a6d4186c3..2fb8f302c 100644 --- a/org.argeo.util/src/org/argeo/util/http/HttpHeader.java +++ b/org.argeo.util/src/org/argeo/util/http/HttpHeader.java @@ -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 index 000000000..ec67cce5e --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java @@ -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; + } + +} -- 2.30.2