]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
Prepare next development cycle
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / auth / RemoteAuthUtils.java
1 package org.argeo.cms.auth;
2
3 import java.security.PrivilegedAction;
4 import java.util.Base64;
5 import java.util.function.Supplier;
6
7 import javax.security.auth.Subject;
8 import javax.security.auth.kerberos.KerberosTicket;
9 import javax.security.auth.login.LoginContext;
10 import javax.security.auth.login.LoginException;
11
12 import org.argeo.api.cms.CmsAuth;
13 import org.argeo.api.cms.CmsLog;
14 import org.argeo.api.cms.CmsSession;
15 import org.argeo.cms.http.HttpHeader;
16 import org.argeo.cms.http.HttpStatus;
17 import org.argeo.cms.internal.runtime.CmsContextImpl;
18 import org.argeo.cms.util.CurrentSubject;
19 import org.ietf.jgss.GSSContext;
20 import org.ietf.jgss.GSSException;
21 import org.ietf.jgss.GSSManager;
22 import org.ietf.jgss.GSSName;
23 import org.ietf.jgss.Oid;
24
25 /** Remote authentication utilities. */
26 public class RemoteAuthUtils {
27 private final static CmsLog log = CmsLog.getLog(RemoteAuthUtils.class);
28
29 static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
30 private final static Oid KERBEROS_OID;
31 // private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID;
32 static {
33 try {
34 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
35 // KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
36 // KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");
37 } catch (GSSException e) {
38 throw new IllegalStateException("Cannot create Kerberos OID", e);
39 }
40 }
41
42 /**
43 * Execute this supplier, using the CMS class loader as context classloader.
44 * Useful to log in to JCR.
45 */
46 public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
47 CmsSession cmsSession = getCmsSession(req);
48 return CurrentSubject.callAs(cmsSession.getSubject(), () -> supplier.get());
49 }
50
51 public static CmsSession getCmsSession(RemoteAuthRequest req) {
52 CmsSession cmsSession = (CmsSession) req.getAttribute(CmsSession.class.getName());
53 if (cmsSession == null)
54 throw new IllegalStateException("Request must have a CMS session attribute");
55 return cmsSession;
56 }
57
58 public static String createGssToken(Subject subject, String service, String server) {
59 if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty())
60 throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated.");
61 return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
62 // !! different format than Kerberos
63 String serverPrinc = service + "@" + server;
64 GSSContext context = null;
65 String tokenStr = null;
66
67 try {
68 // Get service's principal name
69 GSSManager manager = GSSManager.getInstance();
70 // GSSName serverName = manager.createName(serverPrinc,
71 // GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
72 GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE);
73
74 // Get the context for authentication
75 context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
76 // context.requestMutualAuth(true); // Request mutual authentication
77 // context.requestConf(true); // Request confidentiality
78 context.requestCredDeleg(true);
79
80 byte[] token = new byte[0];
81
82 // token is ignored on the first call
83 token = context.initSecContext(token, 0, token.length);
84
85 // Send a token to the server if one was generated by
86 // initSecContext
87 if (token != null) {
88 tokenStr = Base64.getEncoder().encodeToString(token);
89 // complete=true;
90 }
91 return tokenStr;
92
93 } catch (GSSException e) {
94 throw new IllegalStateException("Cannot authenticate to " + serverPrinc, e);
95 }
96 });
97 }
98
99 public static LoginContext anonymousLogin(RemoteAuthRequest remoteAuthRequest,
100 RemoteAuthResponse remoteAuthResponse) {
101 // anonymous
102 ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
103 try {
104 Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
105 LoginContext lc = CmsAuth.ANONYMOUS
106 .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
107 lc.login();
108 return lc;
109 } catch (LoginException e1) {
110 if (log.isDebugEnabled())
111 log.error("Cannot log in as anonymous", e1);
112 return null;
113 } finally {
114 Thread.currentThread().setContextClassLoader(currentContextClassLoader);
115 }
116 }
117
118 public static int askForWwwAuth(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse,
119 String realm, boolean forceBasic) {
120 boolean negotiateFailed = false;
121 if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) != null) {
122 // we already tried, so we give up in order not too loop endlessly
123 if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName())
124 .startsWith(HttpHeader.NEGOTIATE)) {
125 negotiateFailed = true;
126 } else {
127 return HttpStatus.FORBIDDEN.getCode();
128 }
129 }
130
131 // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
132 // realm=\"" + httpAuthRealm + "\"");
133 if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed) {// SPNEGO
134 remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE);
135 // TODO make it configurable ?
136 remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
137 HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
138 } else {
139 remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
140 HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
141 }
142
143 // response.setDateHeader("Date", System.currentTimeMillis());
144 // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
145 // 60 * 60 * 1000));
146 // response.setHeader("Accept-Ranges", "bytes");
147 // response.setHeader("Connection", "Keep-Alive");
148 // response.setHeader("Keep-Alive", "timeout=5, max=97");
149 // response.setContentType("text/html; charset=UTF-8");
150
151 return HttpStatus.UNAUTHORIZED.getCode();
152 }
153
154 private static boolean hasAcceptorCredentials() {
155 return CmsContextImpl.getCmsContext().getAcceptorCredentials() != null;
156 }
157
158 }