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
.security
.auth
.x500
.X500Principal
;
27 import org
.apache
.commons
.logging
.Log
;
28 import org
.apache
.commons
.logging
.LogFactory
;
29 import org
.argeo
.ArgeoException
;
30 import org
.argeo
.cms
.KernelHeader
;
31 import org
.argeo
.cms
.auth
.ArgeoLoginContext
;
32 import org
.argeo
.cms
.widgets
.auth
.DefaultLoginDialog
;
33 import org
.argeo
.eclipse
.ui
.dialogs
.ErrorFeedback
;
34 import org
.argeo
.util
.LocaleUtils
;
35 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
36 import org
.eclipse
.rap
.rwt
.RWT
;
37 import org
.eclipse
.rap
.rwt
.application
.EntryPoint
;
38 import org
.eclipse
.swt
.widgets
.Display
;
39 import org
.eclipse
.ui
.PlatformUI
;
40 import org
.springframework
.security
.authentication
.BadCredentialsException
;
43 * RAP entry point with login capabilities. Once the user has been
44 * authenticated, the workbench is run as a privileged action by the related
47 public class SecureEntryPoint
implements EntryPoint
{
48 private final static Log log
= LogFactory
.getLog(SecureEntryPoint
.class);
51 * From org.springframework.security.context.
52 * HttpSessionContextIntegrationFilter
54 protected static final String SPRING_SECURITY_CONTEXT_KEY
= "SPRING_SECURITY_CONTEXT";
57 * How many seconds to wait before invalidating the session if the user has
60 private Integer loginTimeout
= 1 * 60;
61 // TODO make it configurable
62 /** Default session timeout is 8 hours (European working day length) */
63 private Integer sessionTimeout
= 8 * 60 * 60;
65 /** Override to provide an application specific workbench advisor */
66 protected RapWorkbenchAdvisor
createRapWorkbenchAdvisor(String username
) {
67 return new RapWorkbenchAdvisor(username
);
71 public final int createUI() {
72 // Short login timeout so that the modal dialog login doesn't hang
74 RWT
.getRequest().getSession().setMaxInactiveInterval(loginTimeout
);
76 // Try to load security context thanks to the session processing filter
77 // HttpServletRequest httpRequest = RWT.getRequest();
78 // HttpSession httpSession = httpRequest.getSession();
79 // Object contextFromSessionObject = httpSession
80 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
81 // if (contextFromSessionObject != null)
82 // SecurityContextHolder
83 // .setContext((SecurityContext) contextFromSessionObject);
85 final Display display
= PlatformUI
.createDisplay();
86 Subject subject
= new Subject();
88 final LoginContext loginContext
;
90 CallbackHandler callbackHandler
= new DefaultLoginDialog(
91 display
.getActiveShell());
92 loginContext
= new ArgeoLoginContext(
93 KernelHeader
.LOGIN_CONTEXT_USER
, subject
, callbackHandler
);
94 } catch (LoginException e1
) {
95 throw new ArgeoException("Cannot initialize login context", e1
);
98 tryLogin
: while (subject
.getPrincipals(X500Principal
.class).size() == 0) {
100 loginContext
.login();
101 if (subject
.getPrincipals(X500Principal
.class).size() == 0)
102 throw new ArgeoException("Login succeeded but no auth");// fatal
104 // add security context to session
105 // if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) ==
107 // httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
108 // SecurityContextHolder.getContext());
110 // add thread locale to RWT session
111 if (log
.isTraceEnabled())
112 log
.trace("Locale " + LocaleUtils
.threadLocale
.get());
113 RWT
.setLocale(LocaleUtils
.threadLocale
.get());
115 // once the user is logged in, longer session timeout
116 RWT
.getRequest().getSession()
117 .setMaxInactiveInterval(sessionTimeout
);
119 if (log
.isDebugEnabled())
120 log
.debug("Authenticated " + subject
);
121 } catch (LoginException e
) {
122 BadCredentialsException bce
= wasCausedByBadCredentials(e
);
124 MessageDialog
.openInformation(display
.getActiveShell(),
125 "Bad Credentials", bce
.getMessage());
129 return processLoginDeath(display
, e
);
133 final String username
= subject
.getPrincipals(X500Principal
.class)
134 .iterator().next().getName();
135 // Logout callback when the display is disposed
136 display
.disposeExec(new Runnable() {
138 if (log
.isTraceEnabled())
139 log
.trace("Display disposed");
141 loginContext
.logout();
142 } catch (LoginException e
) {
143 log
.error("Error when logging out", e
);
151 Integer returnCode
= null;
153 returnCode
= Subject
.doAs(subject
, new PrivilegedAction
<Integer
>() {
154 public Integer
run() {
155 RapWorkbenchAdvisor workbenchAdvisor
= createRapWorkbenchAdvisor(username
);
156 int result
= PlatformUI
.createAndRunWorkbench(display
,
158 return new Integer(result
);
161 // Explicit exit from workbench
162 fullLogout(loginContext
, username
);
169 private Integer
processLoginDeath(Display display
, LoginException e
) {
170 // check thread death
171 ThreadDeath td
= wasCausedByThreadDeath(e
);
176 if (!display
.isDisposed()) {
177 ErrorFeedback
.show("Unexpected exception during authentication", e
);
178 // this was not just bad credentials or death thread
179 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
183 throw new ArgeoException(
184 "Unexpected exception during authentication", e
);
189 /** Recursively look for {@link BadCredentialsException} in the root causes. */
190 private BadCredentialsException
wasCausedByBadCredentials(Throwable t
) {
191 if (t
instanceof BadCredentialsException
)
192 return (BadCredentialsException
) t
;
194 if (t
instanceof CredentialNotFoundException
)
195 return new BadCredentialsException("Login canceled");
197 if (t
.getCause() != null)
198 return wasCausedByBadCredentials(t
.getCause());
204 * If there is a {@link ThreadDeath} in the root causes, rethrow it
205 * (important for RAP cleaning mechanism)
207 protected ThreadDeath
wasCausedByThreadDeath(Throwable t
) {
208 if (t
instanceof ThreadDeath
)
209 return (ThreadDeath
) t
;
211 if (t
.getCause() != null)
212 return wasCausedByThreadDeath(t
.getCause());
217 private void fullLogout(LoginContext loginContext
, String username
) {
219 loginContext
.logout();
220 // SecurityContextHolder.clearContext();
222 // HttpServletRequest httpRequest = RWT.getRequest();
223 // HttpSession httpSession = httpRequest.getSession();
224 // httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, null);
225 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
226 log
.info("Logged out " + (username
!= null ? username
: "")
227 + " (THREAD=" + Thread
.currentThread().getId() + ")");
228 } catch (LoginException e
) {
229 log
.error("Error when logging out", e
);