1 package org
.argeo
.cms
.auth
;
3 import java
.security
.PrivilegedAction
;
4 import java
.util
.Base64
;
5 import java
.util
.function
.Supplier
;
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
;
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
;
25 /** Remote authentication utilities. */
26 public class RemoteAuthUtils
{
27 private final static CmsLog log
= CmsLog
.getLog(RemoteAuthUtils
.class);
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;
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
);
43 * Execute this supplier, using the CMS class loader as context classloader.
44 * Useful to log in to JCR.
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());
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");
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;
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
);
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);
80 byte[] token
= new byte[0];
82 // token is ignored on the first call
83 token
= context
.initSecContext(token
, 0, token
.length
);
85 // Send a token to the server if one was generated by
88 tokenStr
= Base64
.getEncoder().encodeToString(token
);
93 } catch (GSSException e
) {
94 throw new IllegalStateException("Cannot authenticate to " + serverPrinc
, e
);
99 public static LoginContext
anonymousLogin(RemoteAuthRequest remoteAuthRequest
,
100 RemoteAuthResponse remoteAuthResponse
) {
102 ClassLoader currentContextClassLoader
= Thread
.currentThread().getContextClassLoader();
104 Thread
.currentThread().setContextClassLoader(RemoteAuthUtils
.class.getClassLoader());
105 LoginContext lc
= CmsAuth
.ANONYMOUS
106 .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest
, remoteAuthResponse
));
109 } catch (LoginException e1
) {
110 if (log
.isDebugEnabled())
111 log
.error("Cannot log in as anonymous", e1
);
114 Thread
.currentThread().setContextClassLoader(currentContextClassLoader
);
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;
127 return HttpStatus
.FORBIDDEN
.getCode();
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
+ "\"");
139 remoteAuthResponse
.setHeader(HttpHeader
.WWW_AUTHENTICATE
.getHeaderName(),
140 HttpHeader
.BASIC
+ " " + HttpHeader
.REALM
+ "=\"" + realm
+ "\"");
143 // response.setDateHeader("Date", System.currentTimeMillis());
144 // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
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");
151 return HttpStatus
.UNAUTHORIZED
.getCode();
154 private static boolean hasAcceptorCredentials() {
155 return CmsContextImpl
.getCmsContext().getAcceptorCredentials() != null;