]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
0354070a9004fe168e8631070c32affece083ae1
[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.LoginException;
22 import javax.security.auth.spi.LoginModule;
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.eclipse.ui.workbench.ErrorFeedback;
30 import org.argeo.security.ui.dialogs.DefaultLoginDialog;
31 import org.argeo.util.LocaleUtils;
32 import org.eclipse.equinox.security.auth.ILoginContext;
33 import org.eclipse.jface.dialogs.MessageDialog;
34 import org.eclipse.rap.rwt.RWT;
35 import org.eclipse.rap.rwt.application.EntryPoint;
36 import org.eclipse.swt.widgets.Display;
37 import org.eclipse.ui.PlatformUI;
38 import org.osgi.framework.BundleContext;
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 BundleContext bc = SecureRapActivator.getActivator().getBundleContext();
98 final LoginModule loginModule = bc.getService(bc
99 .getServiceReference(LoginModule.class));
100 loginModule.initialize(subject,
101 new DefaultLoginDialog(display.getActiveShell()), null, null);
102 tryLogin: while (subject.getPrincipals(Authentication.class).size() == 0) {
103 try {
104 if (!loginModule.login()) {
105 throw new ArgeoException("Login failed");
106 }
107
108 if (subject.getPrincipals(Authentication.class).size() == 0)
109 throw new ArgeoException("Login succeeded but no auth");// fatal
110
111 // add security context to session
112 if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null)
113 httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
114 SecurityContextHolder.getContext());
115 // add thread locale to RWT session
116 log.info("Locale "+LocaleUtils.threadLocale.get());
117 RWT.setLocale(LocaleUtils.threadLocale.get());
118
119 // Once the user is logged in, longer session timeout
120 RWT.getRequest().getSession()
121 .setMaxInactiveInterval(sessionTimeout);
122
123 if (log.isDebugEnabled())
124 log.debug("Authenticated " + subject);
125 } catch (LoginException e) {
126 BadCredentialsException bce = wasCausedByBadCredentials(e);
127 if (bce != null) {
128 MessageDialog.openInformation(display.getActiveShell(),
129 "Bad Credentials", bce.getMessage());
130 // retry login
131 continue tryLogin;
132 }
133 return processLoginDeath(display, e);
134 }
135 }
136
137 final String username = subject.getPrincipals(Authentication.class)
138 .iterator().next().getName();
139 // Logout callback when the display is disposed
140 display.disposeExec(new Runnable() {
141 public void run() {
142 log.debug("Display disposed");
143 // logout(loginContext, username);
144 try {
145 loginModule.logout();
146 } catch (LoginException e) {
147 log.error("Error when logging out", e);
148 }
149 }
150 });
151
152 //
153 // RUN THE WORKBENCH
154 //
155 Integer returnCode = null;
156 try {
157 returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
158 public Integer run() {
159 RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
160 int result = PlatformUI.createAndRunWorkbench(display,
161 workbenchAdvisor);
162 return new Integer(result);
163 }
164 });
165 // logout(loginContext, username);
166 } finally {
167 display.dispose();
168 }
169 return returnCode;
170 }
171
172 private Integer processLoginDeath(Display display, LoginException e) {
173 // check thread death
174 ThreadDeath td = wasCausedByThreadDeath(e);
175 if (td != null) {
176 display.dispose();
177 throw td;
178 }
179 if (!display.isDisposed()) {
180 ErrorFeedback.show("Unexpected exception during authentication", e);
181 // this was not just bad credentials or death thread
182 RWT.getRequest().getSession().setMaxInactiveInterval(1);
183 display.dispose();
184 return -1;
185 } else {
186 throw new ArgeoException(
187 "Unexpected exception during authentication", e);
188 }
189
190 }
191
192 /** Recursively look for {@link BadCredentialsException} in the root causes. */
193 private BadCredentialsException wasCausedByBadCredentials(Throwable t) {
194 if (t instanceof BadCredentialsException)
195 return (BadCredentialsException) t;
196
197 if (t.getCause() != null)
198 return wasCausedByBadCredentials(t.getCause());
199 else
200 return null;
201 }
202
203 /**
204 * If there is a {@link ThreadDeath} in the root causes, rethrow it
205 * (important for RAP cleaning mechanism)
206 */
207 protected ThreadDeath wasCausedByThreadDeath(Throwable t) {
208 if (t instanceof ThreadDeath)
209 return (ThreadDeath) t;
210
211 if (t.getCause() != null)
212 return wasCausedByThreadDeath(t.getCause());
213 else
214 return null;
215 }
216
217 protected void logout(ILoginContext secureContext, String username) {
218 try {
219 HttpServletRequest httpRequest = RWT.getRequest();
220 HttpSession httpSession = httpRequest.getSession();
221 httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, null);
222 RWT.getRequest().getSession().setMaxInactiveInterval(1);
223 SecurityContextHolder.clearContext();
224 secureContext.logout();
225 log.info("Logged out " + (username != null ? username : "")
226 + " (THREAD=" + Thread.currentThread().getId() + ")");
227 } catch (LoginException e) {
228 log.error("Erorr when logging out", e);
229 }
230 }
231 }