]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
Prepare next development cycle
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / auth / RemoteSessionLoginModule.java
1 package org.argeo.cms.auth;
2
3 import java.io.IOException;
4 import java.security.cert.X509Certificate;
5 import java.util.Base64;
6 import java.util.Locale;
7 import java.util.Map;
8 import java.util.StringTokenizer;
9
10 import javax.security.auth.Subject;
11 import javax.security.auth.callback.Callback;
12 import javax.security.auth.callback.CallbackHandler;
13 import javax.security.auth.callback.UnsupportedCallbackException;
14 import javax.security.auth.login.LoginException;
15 import javax.security.auth.spi.LoginModule;
16
17 import org.argeo.api.cms.CmsLog;
18 import org.argeo.cms.CmsDeployProperty;
19 import org.argeo.cms.http.HttpHeader;
20 import org.argeo.cms.internal.auth.CmsSessionImpl;
21 import org.argeo.cms.internal.runtime.CmsContextImpl;
22 import org.argeo.cms.internal.runtime.CmsStateImpl;
23 import org.osgi.service.useradmin.Authorization;
24
25 /** Use a remote session as the basis for authentication. */
26 public class RemoteSessionLoginModule implements LoginModule {
27 private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
28
29 private Subject subject = null;
30 private CallbackHandler callbackHandler = null;
31 private Map<String, Object> sharedState = null;
32
33 private RemoteAuthRequest request = null;
34 private RemoteAuthResponse response = null;
35
36 private Authorization authorization;
37 private Locale locale;
38
39 @SuppressWarnings("unchecked")
40 @Override
41 public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
42 Map<String, ?> options) {
43 this.subject = subject;
44 this.callbackHandler = callbackHandler;
45 this.sharedState = (Map<String, Object>) sharedState;
46 }
47
48 @Override
49 public boolean login() throws LoginException {
50 if (callbackHandler == null)
51 return false;
52 RemoteAuthCallback remoteAuthCallback = new RemoteAuthCallback();
53 try {
54 callbackHandler.handle(new Callback[] { remoteAuthCallback });
55 } catch (IOException e) {
56 throw new LoginException("Cannot handle http callback: " + e.getMessage());
57 } catch (UnsupportedCallbackException e) {
58 return false;
59 }
60 request = remoteAuthCallback.getRequest();
61 if (request == null) {
62 RemoteAuthSession httpSession = remoteAuthCallback.getHttpSession();
63 if (httpSession == null)
64 return false;
65 // TODO factorize with below
66 String httpSessionId = httpSession.getId();
67 CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
68 if (cmsSession != null && !cmsSession.isAnonymous()) {
69 authorization = cmsSession.getAuthorization();
70 locale = cmsSession.getLocale();
71 if (log.isTraceEnabled())
72 log.trace("Retrieved authorization from " + cmsSession);
73 }
74 } else {
75 authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
76 if (authorization == null) {// search by session ID
77 RemoteAuthSession httpSession = request.getSession();
78 if (httpSession != null) {
79 String httpSessionId = httpSession.getId();
80 CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
81 if (cmsSession != null && !cmsSession.isAnonymous()) {
82 authorization = cmsSession.getAuthorization();
83 locale = cmsSession.getLocale();
84 if (log.isTraceEnabled())
85 log.trace("Retrieved authorization from " + cmsSession);
86 }
87 }else {
88 request.createSession();
89 }
90 }
91 sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
92 extractHttpAuth(request);
93 extractClientCertificate(request);
94 }
95 if (authorization == null) {
96 if (log.isTraceEnabled())
97 log.trace("HTTP login: " + false);
98 return false;
99 } else {
100 if (log.isTraceEnabled())
101 log.trace("HTTP login: " + true);
102 request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
103 return true;
104 }
105 }
106
107 @Override
108 public boolean commit() throws LoginException {
109 byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
110 if (outToken != null) {
111 response.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
112 "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
113 }
114
115 if (authorization != null) {
116 // Locale locale = request.getLocale();
117 if (locale == null && request != null)
118 locale = request.getLocale();
119 if (locale != null)
120 subject.getPublicCredentials().add(locale);
121 CmsAuthUtils.addAuthorization(subject, authorization);
122 CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
123 cleanUp();
124 return true;
125 } else {
126 cleanUp();
127 return false;
128 }
129 }
130
131 @Override
132 public boolean abort() throws LoginException {
133 cleanUp();
134 return false;
135 }
136
137 private void cleanUp() {
138 authorization = null;
139 request = null;
140 }
141
142 @Override
143 public boolean logout() throws LoginException {
144 cleanUp();
145 return true;
146 }
147
148 private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
149 String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName());
150 extractHttpAuth(authHeader);
151 }
152
153 private void extractHttpAuth(String authHeader) {
154 if (authHeader != null) {
155 StringTokenizer st = new StringTokenizer(authHeader);
156 if (st.hasMoreTokens()) {
157 String basic = st.nextToken();
158 if (basic.equalsIgnoreCase(HttpHeader.BASIC)) {
159 try {
160 // TODO manipulate char[]
161 Base64.Decoder decoder = Base64.getDecoder();
162 String credentials = new String(decoder.decode(st.nextToken()), "UTF-8");
163 // log.debug("Credentials: " + credentials);
164 int p = credentials.indexOf(":");
165 if (p != -1) {
166 final String login = credentials.substring(0, p).trim();
167 final char[] password = credentials.substring(p + 1).trim().toCharArray();
168 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
169 sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
170 } else {
171 throw new IllegalStateException("Invalid authentication token");
172 }
173 } catch (Exception e) {
174 throw new IllegalStateException("Couldn't retrieve authentication", e);
175 }
176 } else if (basic.equalsIgnoreCase(HttpHeader.NEGOTIATE)) {
177 String spnegoToken = st.nextToken();
178 Base64.Decoder decoder = Base64.getDecoder();
179 byte[] authToken = decoder.decode(spnegoToken);
180 sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
181 }
182 }
183 }
184 }
185
186 private void extractClientCertificate(RemoteAuthRequest req) {
187 X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
188 if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
189 String certDn = certs[0].getSubjectX500Principal().getName();
190 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
191 sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
192 if (log.isDebugEnabled())
193 log.debug("Client certificate " + certDn + " verified by servlet container");
194 } // Reverse proxy verified the client certificate
195 String clientDnHttpHeader = CmsStateImpl.getDeployProperty(CmsContextImpl.getCmsContext().getCmsState(),
196 CmsDeployProperty.HTTP_PROXY_SSL_HEADER_DN);
197 if (clientDnHttpHeader != null) {
198 String certDn = req.getHeader(clientDnHttpHeader);
199 // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
200 // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
201 if (certDn != null && !certDn.trim().equals("(null)")) {
202 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
203 sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
204 if (log.isDebugEnabled())
205 log.debug("Client certificate " + certDn + " verified by reverse proxy");
206 }
207 }
208 }
209
210 }