]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java
Improve CMS UI
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / auth / HttpSessionLoginModule.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.Collection;
7 import java.util.Locale;
8 import java.util.Map;
9 import java.util.StringTokenizer;
10
11 import javax.security.auth.Subject;
12 import javax.security.auth.callback.Callback;
13 import javax.security.auth.callback.CallbackHandler;
14 import javax.security.auth.callback.UnsupportedCallbackException;
15 import javax.security.auth.login.LoginException;
16 import javax.security.auth.spi.LoginModule;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import javax.servlet.http.HttpSession;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.argeo.cms.CmsException;
24 import org.argeo.cms.internal.kernel.Activator;
25 import org.osgi.framework.BundleContext;
26 import org.osgi.framework.FrameworkUtil;
27 import org.osgi.framework.InvalidSyntaxException;
28 import org.osgi.framework.ServiceReference;
29 import org.osgi.service.http.HttpContext;
30 import org.osgi.service.useradmin.Authorization;
31
32 public class HttpSessionLoginModule implements LoginModule {
33 private final static Log log = LogFactory.getLog(HttpSessionLoginModule.class);
34
35 private Subject subject = null;
36 private CallbackHandler callbackHandler = null;
37 private Map<String, Object> sharedState = null;
38
39 private HttpServletRequest request = null;
40 private HttpServletResponse response = null;
41
42 private BundleContext bc;
43
44 private Authorization authorization;
45 private Locale locale;
46
47 @SuppressWarnings("unchecked")
48 @Override
49 public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
50 Map<String, ?> options) {
51 bc = FrameworkUtil.getBundle(HttpSessionLoginModule.class).getBundleContext();
52 assert bc != null;
53 this.subject = subject;
54 this.callbackHandler = callbackHandler;
55 this.sharedState = (Map<String, Object>) sharedState;
56 }
57
58 @Override
59 public boolean login() throws LoginException {
60 if (callbackHandler == null)
61 return false;
62 HttpRequestCallback httpCallback = new HttpRequestCallback();
63 try {
64 callbackHandler.handle(new Callback[] { httpCallback });
65 } catch (IOException e) {
66 throw new LoginException("Cannot handle http callback: " + e.getMessage());
67 } catch (UnsupportedCallbackException e) {
68 return false;
69 }
70 request = httpCallback.getRequest();
71 if (request == null)
72 return false;
73 authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
74 if (authorization == null) {// search by session ID
75 HttpSession httpSession = request.getSession(false);
76 if (httpSession == null) {
77 // TODO make sure this is always safe
78 if (log.isTraceEnabled())
79 log.trace("Create http session");
80 httpSession = request.getSession(true);
81 }
82 String httpSessionId = httpSession.getId();
83 // authorization = (Authorization)
84 // request.getSession().getAttribute(HttpContext.AUTHORIZATION);
85 // if (authorization == null) {
86 Collection<ServiceReference<CmsSession>> sr;
87 try {
88 sr = bc.getServiceReferences(CmsSession.class,
89 "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
90 } catch (InvalidSyntaxException e) {
91 throw new CmsException("Cannot get CMS session for id " + httpSessionId, e);
92 }
93 if (sr.size() == 1) {
94 CmsSession cmsSession = bc.getService(sr.iterator().next());
95 locale = cmsSession.getLocale();
96 authorization = cmsSession.getAuthorization();
97 if (authorization.getName() == null)
98 authorization = null;// anonymous is not sufficient
99 if (log.isTraceEnabled())
100 log.trace("Retrieved authorization from " + cmsSession);
101 } else if (sr.size() == 0)
102 authorization = null;
103 else
104 throw new CmsException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
105
106 }
107 sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
108 extractHttpAuth(request);
109 extractClientCertificate(request);
110 if (authorization == null) {
111 return false;
112 } else {
113 return true;
114 }
115 }
116
117 @Override
118 public boolean commit() throws LoginException {
119 byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
120 if (outToken != null) {
121 response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
122 "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
123 }
124
125 if (authorization != null) {
126 // Locale locale = request.getLocale();
127 if (locale == null)
128 locale = request.getLocale();
129 subject.getPublicCredentials().add(locale);
130 CmsAuthUtils.addAuthorization(subject, authorization, locale, request);
131 CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
132 cleanUp();
133 return true;
134 } else {
135 cleanUp();
136 return false;
137 }
138 }
139
140 @Override
141 public boolean abort() throws LoginException {
142 cleanUp();
143 return false;
144 }
145
146 private void cleanUp() {
147 authorization = null;
148 request = null;
149 }
150
151 @Override
152 public boolean logout() throws LoginException {
153 cleanUp();
154 return true;
155 }
156
157 private void extractHttpAuth(final HttpServletRequest httpRequest) {
158 String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
159 if (authHeader != null) {
160 StringTokenizer st = new StringTokenizer(authHeader);
161 if (st.hasMoreTokens()) {
162 String basic = st.nextToken();
163 if (basic.equalsIgnoreCase("Basic")) {
164 try {
165 // TODO manipulate char[]
166 Base64.Decoder decoder = Base64.getDecoder();
167 String credentials = new String(decoder.decode(st.nextToken()), "UTF-8");
168 // log.debug("Credentials: " + credentials);
169 int p = credentials.indexOf(":");
170 if (p != -1) {
171 final String login = credentials.substring(0, p).trim();
172 final char[] password = credentials.substring(p + 1).trim().toCharArray();
173 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
174 sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
175 } else {
176 throw new CmsException("Invalid authentication token");
177 }
178 } catch (Exception e) {
179 throw new CmsException("Couldn't retrieve authentication", e);
180 }
181 } else if (basic.equalsIgnoreCase("Negotiate")) {
182 String spnegoToken = st.nextToken();
183 Base64.Decoder decoder = Base64.getDecoder();
184 byte[] authToken = decoder.decode(spnegoToken);
185 sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
186 }
187 }
188 }
189
190 // auth token
191 // String mail = request.getParameter(LdapAttrs.mail.name());
192 // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
193 // if (authPassword != null) {
194 // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
195 // if (mail != null)
196 // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
197 // }
198 }
199
200 private void extractClientCertificate(HttpServletRequest req) {
201 X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
202 if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
203 String certDn = certs[0].getSubjectX500Principal().getName();
204 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
205 sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
206 if (log.isDebugEnabled())
207 log.debug("Client certificate " + certDn + " verified by servlet container");
208 } // Reverse proxy verified the client certificate
209 String clientDnHttpHeader = Activator.getHttpProxySslHeader();
210 if (clientDnHttpHeader != null) {
211 String certDn = req.getHeader(clientDnHttpHeader);
212 // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
213 // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
214 if (certDn != null && !certDn.trim().equals("(null)")) {
215 sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
216 sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
217 if (log.isDebugEnabled())
218 log.debug("Client certificate " + certDn + " verified by reverse proxy");
219 }
220 }
221 }
222
223 }