]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
Improve login mechanism, based on JAAS
[lgpl/argeo-commons.git] / org.argeo.security.ui.rap / src / org / argeo / security / ui / rap / SecureEntryPoint.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.security.ui.rap;
17
18 import java.security.PrivilegedAction;
19
20 import javax.security.auth.Subject;
21 import javax.security.auth.login.LoginContext;
22 import javax.security.auth.login.LoginException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpSession;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.argeo.ArgeoException;
29 import org.argeo.cms.KernelHeader;
30 import org.argeo.eclipse.ui.workbench.ErrorFeedback;
31 import org.argeo.security.login.LoginCanceledException;
32 import org.argeo.security.ui.dialogs.DefaultLoginDialog;
33 import org.argeo.util.LocaleUtils;
34 import org.eclipse.jface.dialogs.MessageDialog;
35 import org.eclipse.rap.rwt.RWT;
36 import org.eclipse.rap.rwt.application.EntryPoint;
37 import org.eclipse.swt.widgets.Display;
38 import org.eclipse.ui.PlatformUI;
39 import org.springframework.security.authentication.BadCredentialsException;
40 import org.springframework.security.core.Authentication;
41 import org.springframework.security.core.context.SecurityContext;
42 import org.springframework.security.core.context.SecurityContextHolder;
43
44 /**
45 * RAP entry point with login capabilities. Once the user has been
46 * authenticated, the workbench is run as a privileged action by the related
47 * subject.
48 */
49 public class SecureEntryPoint implements EntryPoint {
50 private final static Log log = LogFactory.getLog(SecureEntryPoint.class);
51
52 /**
53 * From org.springframework.security.context.
54 * HttpSessionContextIntegrationFilter
55 */
56 protected static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
57
58 /**
59 * How many seconds to wait before invalidating the session if the user has
60 * not yet logged in.
61 */
62 private Integer loginTimeout = 1 * 60;
63 // TODO make it configurable
64 /** Default session timeout is 8 hours (European working day length) */
65 private Integer sessionTimeout = 8 * 60 * 60;
66
67 /** Override to provide an application specific workbench advisor */
68 protected RapWorkbenchAdvisor createRapWorkbenchAdvisor(String username) {
69 return new RapWorkbenchAdvisor(username);
70 }
71
72 @Override
73 public final int createUI() {
74 // Short login timeout so that the modal dialog login doesn't hang
75 // around too long
76 RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout);
77
78 // Try to load security context thanks to the session processing filter
79 HttpServletRequest httpRequest = RWT.getRequest();
80 HttpSession httpSession = httpRequest.getSession();
81 Object contextFromSessionObject = httpSession
82 .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
83 if (contextFromSessionObject != null)
84 SecurityContextHolder
85 .setContext((SecurityContext) contextFromSessionObject);
86
87 // if (log.isDebugEnabled())
88 // log.debug("THREAD=" + Thread.currentThread().getId()
89 // + ", sessionStore=" + RWT.getSessionStore().getId()
90 // + ", remote user=" + httpRequest.getRemoteUser());
91
92 // create display
93 final Display display = PlatformUI.createDisplay();
94 Subject subject = new Subject();
95
96 // log in
97 Thread.currentThread().setContextClassLoader(
98 getClass().getClassLoader());
99 final LoginContext loginContext;
100 try {
101 loginContext = new LoginContext(KernelHeader.LOGIN_CONTEXT_USER,
102 subject, new DefaultLoginDialog(display.getActiveShell()));
103 } catch (LoginException e1) {
104 throw new ArgeoException("Cannot initialize login context", e1);
105 }
106
107 tryLogin: while (subject.getPrincipals(Authentication.class).size() == 0) {
108 try {
109 loginContext.login();
110 // if () {
111 // throw new ArgeoException("Login failed");
112 // }
113
114 if (subject.getPrincipals(Authentication.class).size() == 0)
115 throw new ArgeoException("Login succeeded but no auth");// fatal
116
117 // add security context to session
118 if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null)
119 httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
120 SecurityContextHolder.getContext());
121 // add thread locale to RWT session
122 log.info("Locale " + LocaleUtils.threadLocale.get());
123 RWT.setLocale(LocaleUtils.threadLocale.get());
124
125 // Once the user is logged in, longer session timeout
126 RWT.getRequest().getSession()
127 .setMaxInactiveInterval(sessionTimeout);
128
129 if (log.isDebugEnabled())
130 log.debug("Authenticated " + subject);
131 } catch (LoginException e) {
132 BadCredentialsException bce = wasCausedByBadCredentials(e);
133 if (bce != null) {
134 MessageDialog.openInformation(display.getActiveShell(),
135 "Bad Credentials", bce.getMessage());
136 // retry login
137 continue tryLogin;
138 }
139 return processLoginDeath(display, e);
140 }
141 }
142
143 final String username = subject.getPrincipals(Authentication.class)
144 .iterator().next().getName();
145 // Logout callback when the display is disposed
146 display.disposeExec(new Runnable() {
147 public void run() {
148 log.debug("Display disposed");
149 // logout(loginContext, username);
150 try {
151 loginContext.logout();
152 } catch (LoginException e) {
153 log.error("Error when logging out", e);
154 }
155 }
156 });
157
158 //
159 // RUN THE WORKBENCH
160 //
161 Integer returnCode = null;
162 try {
163 returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
164 public Integer run() {
165 RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
166 int result = PlatformUI.createAndRunWorkbench(display,
167 workbenchAdvisor);
168 return new Integer(result);
169 }
170 });
171 // Explicit exit from workbench
172 logout(loginContext, username);
173 } finally {
174 display.dispose();
175 }
176 return returnCode;
177 }
178
179 private Integer processLoginDeath(Display display, LoginException e) {
180 // check thread death
181 ThreadDeath td = wasCausedByThreadDeath(e);
182 if (td != null) {
183 display.dispose();
184 throw td;
185 }
186 if (!display.isDisposed()) {
187 ErrorFeedback.show("Unexpected exception during authentication", e);
188 // this was not just bad credentials or death thread
189 RWT.getRequest().getSession().setMaxInactiveInterval(1);
190 display.dispose();
191 return -1;
192 } else {
193 throw new ArgeoException(
194 "Unexpected exception during authentication", e);
195 }
196
197 }
198
199 /** Recursively look for {@link BadCredentialsException} in the root causes. */
200 private BadCredentialsException wasCausedByBadCredentials(Throwable t) {
201 if (t instanceof BadCredentialsException)
202 return (BadCredentialsException) t;
203
204 if (t instanceof LoginCanceledException)
205 return new BadCredentialsException("Login canceled");
206
207 if (t.getCause() != null)
208 return wasCausedByBadCredentials(t.getCause());
209 else
210 return null;
211 }
212
213 /**
214 * If there is a {@link ThreadDeath} in the root causes, rethrow it
215 * (important for RAP cleaning mechanism)
216 */
217 protected ThreadDeath wasCausedByThreadDeath(Throwable t) {
218 if (t instanceof ThreadDeath)
219 return (ThreadDeath) t;
220
221 if (t.getCause() != null)
222 return wasCausedByThreadDeath(t.getCause());
223 else
224 return null;
225 }
226
227 private void logout(LoginContext loginContext, String username) {
228 try {
229 loginContext.logout();
230 SecurityContextHolder.clearContext();
231
232 HttpServletRequest httpRequest = RWT.getRequest();
233 HttpSession httpSession = httpRequest.getSession();
234 httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, null);
235 RWT.getRequest().getSession().setMaxInactiveInterval(1);
236 log.info("Logged out " + (username != null ? username : "")
237 + " (THREAD=" + Thread.currentThread().getId() + ")");
238 } catch (LoginException e) {
239 log.error("Erorr when logging out", e);
240 }
241 }
242 }