2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.security
.ui
.rap
;
18 import java
.security
.PrivilegedAction
;
20 import javax
.security
.auth
.Subject
;
21 import javax
.security
.auth
.callback
.CallbackHandler
;
22 import javax
.security
.auth
.login
.CredentialNotFoundException
;
23 import javax
.security
.auth
.login
.LoginContext
;
24 import javax
.security
.auth
.login
.LoginException
;
25 import javax
.servlet
.http
.HttpServletRequest
;
26 import javax
.servlet
.http
.HttpSession
;
28 import org
.apache
.commons
.logging
.Log
;
29 import org
.apache
.commons
.logging
.LogFactory
;
30 import org
.argeo
.ArgeoException
;
31 import org
.argeo
.cms
.KernelHeader
;
32 import org
.argeo
.cms
.auth
.ArgeoLoginContext
;
33 import org
.argeo
.eclipse
.ui
.dialogs
.ErrorFeedback
;
34 import org
.argeo
.security
.ui
.auth
.DefaultLoginDialog
;
35 import org
.argeo
.util
.LocaleUtils
;
36 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
37 import org
.eclipse
.rap
.rwt
.RWT
;
38 import org
.eclipse
.rap
.rwt
.application
.EntryPoint
;
39 import org
.eclipse
.swt
.widgets
.Display
;
40 import org
.eclipse
.ui
.PlatformUI
;
41 import org
.springframework
.security
.authentication
.BadCredentialsException
;
42 import org
.springframework
.security
.core
.Authentication
;
43 import org
.springframework
.security
.core
.context
.SecurityContext
;
44 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
47 * RAP entry point with login capabilities. Once the user has been
48 * authenticated, the workbench is run as a privileged action by the related
51 public class SecureEntryPoint
implements EntryPoint
{
52 private final static Log log
= LogFactory
.getLog(SecureEntryPoint
.class);
55 * From org.springframework.security.context.
56 * HttpSessionContextIntegrationFilter
58 protected static final String SPRING_SECURITY_CONTEXT_KEY
= "SPRING_SECURITY_CONTEXT";
61 * How many seconds to wait before invalidating the session if the user has
64 private Integer loginTimeout
= 1 * 60;
65 // TODO make it configurable
66 /** Default session timeout is 8 hours (European working day length) */
67 private Integer sessionTimeout
= 8 * 60 * 60;
69 /** Override to provide an application specific workbench advisor */
70 protected RapWorkbenchAdvisor
createRapWorkbenchAdvisor(String username
) {
71 return new RapWorkbenchAdvisor(username
);
75 public final int createUI() {
76 // Short login timeout so that the modal dialog login doesn't hang
78 RWT
.getRequest().getSession().setMaxInactiveInterval(loginTimeout
);
80 // Try to load security context thanks to the session processing filter
81 HttpServletRequest httpRequest
= RWT
.getRequest();
82 HttpSession httpSession
= httpRequest
.getSession();
83 Object contextFromSessionObject
= httpSession
84 .getAttribute(SPRING_SECURITY_CONTEXT_KEY
);
85 if (contextFromSessionObject
!= null)
87 .setContext((SecurityContext
) contextFromSessionObject
);
89 // if (log.isDebugEnabled())
90 // log.debug("THREAD=" + Thread.currentThread().getId()
91 // + ", sessionStore=" + RWT.getSessionStore().getId()
92 // + ", remote user=" + httpRequest.getRemoteUser());
95 final Display display
= PlatformUI
.createDisplay();
96 Subject subject
= new Subject();
99 // Thread.currentThread().setContextClassLoader(
100 // getClass().getClassLoader());
101 final LoginContext loginContext
;
103 CallbackHandler callbackHandler
= new DefaultLoginDialog(
104 display
.getActiveShell());
105 loginContext
= new ArgeoLoginContext(
106 KernelHeader
.LOGIN_CONTEXT_USER
, subject
, callbackHandler
);
107 } catch (LoginException e1
) {
108 throw new ArgeoException("Cannot initialize login context", e1
);
111 tryLogin
: while (subject
.getPrincipals(Authentication
.class).size() == 0) {
113 loginContext
.login();
115 // throw new ArgeoException("Login failed");
118 if (subject
.getPrincipals(Authentication
.class).size() == 0)
119 throw new ArgeoException("Login succeeded but no auth");// fatal
121 // add security context to session
122 if (httpSession
.getAttribute(SPRING_SECURITY_CONTEXT_KEY
) == null)
123 httpSession
.setAttribute(SPRING_SECURITY_CONTEXT_KEY
,
124 SecurityContextHolder
.getContext());
125 // add thread locale to RWT session
126 if (log
.isTraceEnabled())
127 log
.trace("Locale " + LocaleUtils
.threadLocale
.get());
128 RWT
.setLocale(LocaleUtils
.threadLocale
.get());
130 // Once the user is logged in, longer session timeout
131 RWT
.getRequest().getSession()
132 .setMaxInactiveInterval(sessionTimeout
);
134 if (log
.isDebugEnabled())
135 log
.debug("Authenticated " + subject
);
136 } catch (LoginException e
) {
137 BadCredentialsException bce
= wasCausedByBadCredentials(e
);
139 MessageDialog
.openInformation(display
.getActiveShell(),
140 "Bad Credentials", bce
.getMessage());
144 return processLoginDeath(display
, e
);
148 final String username
= subject
.getPrincipals(Authentication
.class)
149 .iterator().next().getName();
150 // Logout callback when the display is disposed
151 display
.disposeExec(new Runnable() {
153 if (log
.isTraceEnabled())
154 log
.trace("Display disposed");
156 loginContext
.logout();
157 } catch (LoginException e
) {
158 log
.error("Error when logging out", e
);
166 Integer returnCode
= null;
168 returnCode
= Subject
.doAs(subject
, new PrivilegedAction
<Integer
>() {
169 public Integer
run() {
170 RapWorkbenchAdvisor workbenchAdvisor
= createRapWorkbenchAdvisor(username
);
171 int result
= PlatformUI
.createAndRunWorkbench(display
,
173 return new Integer(result
);
176 // Explicit exit from workbench
177 fullLogout(loginContext
, username
);
184 private Integer
processLoginDeath(Display display
, LoginException e
) {
185 // check thread death
186 ThreadDeath td
= wasCausedByThreadDeath(e
);
191 if (!display
.isDisposed()) {
192 ErrorFeedback
.show("Unexpected exception during authentication", e
);
193 // this was not just bad credentials or death thread
194 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
198 throw new ArgeoException(
199 "Unexpected exception during authentication", e
);
204 /** Recursively look for {@link BadCredentialsException} in the root causes. */
205 private BadCredentialsException
wasCausedByBadCredentials(Throwable t
) {
206 if (t
instanceof BadCredentialsException
)
207 return (BadCredentialsException
) t
;
209 if (t
instanceof CredentialNotFoundException
)
210 return new BadCredentialsException("Login canceled");
212 if (t
.getCause() != null)
213 return wasCausedByBadCredentials(t
.getCause());
219 * If there is a {@link ThreadDeath} in the root causes, rethrow it
220 * (important for RAP cleaning mechanism)
222 protected ThreadDeath
wasCausedByThreadDeath(Throwable t
) {
223 if (t
instanceof ThreadDeath
)
224 return (ThreadDeath
) t
;
226 if (t
.getCause() != null)
227 return wasCausedByThreadDeath(t
.getCause());
232 private void fullLogout(LoginContext loginContext
, String username
) {
234 loginContext
.logout();
235 SecurityContextHolder
.clearContext();
237 HttpServletRequest httpRequest
= RWT
.getRequest();
238 HttpSession httpSession
= httpRequest
.getSession();
239 httpSession
.setAttribute(SPRING_SECURITY_CONTEXT_KEY
, null);
240 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
241 log
.info("Logged out " + (username
!= null ? username
: "")
242 + " (THREAD=" + Thread
.currentThread().getId() + ")");
243 } catch (LoginException e
) {
244 log
.error("Erorr when logging out", e
);