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
.AccessControlContext
;
19 import java
.security
.AccessController
;
20 import java
.security
.PrivilegedAction
;
22 import javax
.security
.auth
.Subject
;
23 import javax
.security
.auth
.login
.CredentialNotFoundException
;
24 import javax
.security
.auth
.login
.FailedLoginException
;
25 import javax
.security
.auth
.login
.LoginContext
;
26 import javax
.security
.auth
.login
.LoginException
;
27 import javax
.security
.auth
.x500
.X500Principal
;
28 import javax
.servlet
.http
.HttpServletRequest
;
29 import javax
.servlet
.http
.HttpSession
;
31 import org
.apache
.commons
.logging
.Log
;
32 import org
.apache
.commons
.logging
.LogFactory
;
33 import org
.argeo
.ArgeoException
;
34 import org
.argeo
.cms
.auth
.AuthConstants
;
35 import org
.argeo
.cms
.auth
.ThreadDeathLoginException
;
36 import org
.argeo
.cms
.widgets
.auth
.DefaultLoginDialog
;
37 import org
.argeo
.eclipse
.ui
.dialogs
.ErrorFeedback
;
38 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
39 import org
.eclipse
.rap
.rwt
.RWT
;
40 import org
.eclipse
.rap
.rwt
.application
.EntryPoint
;
41 import org
.eclipse
.swt
.widgets
.Display
;
42 import org
.eclipse
.ui
.PlatformUI
;
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
50 public class SecureEntryPoint
implements EntryPoint
{
51 final static String ACCESS_CONTROL_CONTEXT
= "org.argeo.node.accessControlContext";
52 private final static Log log
= LogFactory
.getLog(SecureEntryPoint
.class);
55 * How many seconds to wait before invalidating the session if the user has
58 private Integer loginTimeout
= 1 * 60;
59 // TODO make it configurable
60 /** Default session timeout is 8 hours (European working day length) */
61 private Integer sessionTimeout
= 8 * 60 * 60;
63 /** Override to provide an application specific workbench advisor */
64 protected RapWorkbenchAdvisor
createRapWorkbenchAdvisor(String username
) {
65 return new RapWorkbenchAdvisor(username
);
69 public final int createUI() {
70 // Short login timeout so that the modal dialog login doesn't hang
72 RWT
.getRequest().getSession().setMaxInactiveInterval(loginTimeout
);
74 final Display display
= PlatformUI
.createDisplay();
76 // load context from session
77 HttpServletRequest httpRequest
= RWT
.getRequest();
78 final HttpSession httpSession
= httpRequest
.getSession();
79 AccessControlContext acc
= (AccessControlContext
) httpSession
80 .getAttribute(ACCESS_CONTROL_CONTEXT
);
82 final Subject subject
;
84 && Subject
.getSubject(acc
).getPrincipals(X500Principal
.class)
86 subject
= Subject
.getSubject(acc
);
88 subject
= new Subject();
90 final LoginContext loginContext
;
91 DefaultLoginDialog callbackHandler
;
93 callbackHandler
= new DefaultLoginDialog(
94 display
.getActiveShell());
95 loginContext
= new LoginContext(
96 AuthConstants
.LOGIN_CONTEXT_USER
, subject
,
98 } catch (LoginException e1
) {
99 throw new ArgeoException("Cannot initialize login context", e1
);
102 tryLogin
: while (subject
.getPrincipals(X500Principal
.class).size() == 0) {
104 loginContext
.login();
105 if (subject
.getPrincipals(X500Principal
.class).size() == 0)
106 throw new ArgeoException("Login succeeded but no auth");// fatal
108 // add thread locale to RWT session
109 // if (log.isTraceEnabled())
110 // log.trace("Locale " + LocaleUtils.threadLocale.get());
111 // RWT.setLocale(LocaleUtils.threadLocale.get());
113 // once the user is logged in, longer session timeout
114 RWT
.getRequest().getSession()
115 .setMaxInactiveInterval(sessionTimeout
);
117 if (log
.isDebugEnabled())
118 log
.debug("Authenticated " + subject
);
119 } catch (FailedLoginException e
) {
120 MessageDialog
.openInformation(display
.getActiveShell(),
121 "Bad Credentials", e
.getMessage());
124 } catch (CredentialNotFoundException e
) {
125 MessageDialog
.openInformation(display
.getActiveShell(),
126 "No Credentials", e
.getMessage());
129 } catch (LoginException e
) {
130 callbackHandler
.getShell().dispose();
131 return processLoginDeath(display
, e
);
135 final String username
= subject
.getPrincipals(X500Principal
.class)
136 .iterator().next().getName();
137 // Logout callback when the display is disposed
138 display
.disposeExec(new Runnable() {
140 if (log
.isTraceEnabled())
141 log
.trace("Display disposed");
143 LoginContext loginContext
= new LoginContext(
144 AuthConstants
.LOGIN_CONTEXT_USER
, subject
);
145 loginContext
.logout();
146 } catch (LoginException e
) {
147 log
.error("Error when logging out", e
);
155 Integer returnCode
= null;
157 returnCode
= Subject
.doAs(subject
, new PrivilegedAction
<Integer
>() {
158 public Integer
run() {
159 // add security context to session
160 httpSession
.setAttribute(ACCESS_CONTROL_CONTEXT
,
161 AccessController
.getContext());
164 RapWorkbenchAdvisor workbenchAdvisor
= createRapWorkbenchAdvisor(username
);
165 int result
= PlatformUI
.createAndRunWorkbench(display
,
167 return new Integer(result
);
170 // Explicit exit from workbench
171 fullLogout(subject
, username
);
178 private Integer
processLoginDeath(Display display
, Throwable e
) {
179 // check thread death
180 ThreadDeath td
= wasCausedByThreadDeath(e
);
185 if (!display
.isDisposed()) {
186 ErrorFeedback
.show("Unexpected exception during authentication", e
);
187 // this was not just bad credentials or death thread
188 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
192 throw new ArgeoException(
193 "Unexpected exception during authentication", e
);
199 * If there is a {@link ThreadDeath} in the root causes, rethrow it
200 * (important for RAP cleaning mechanism)
202 protected ThreadDeath
wasCausedByThreadDeath(Throwable t
) {
203 if (t
instanceof ThreadDeath
)
204 return (ThreadDeath
) t
;
205 if (t
instanceof ThreadDeathLoginException
)
206 return ((ThreadDeathLoginException
) t
).getThreadDeath();
207 if (t
.getCause() != null)
208 return wasCausedByThreadDeath(t
.getCause());
213 private void fullLogout(Subject subject
, String username
) {
215 LoginContext loginContext
= new LoginContext(
216 AuthConstants
.LOGIN_CONTEXT_USER
, subject
);
217 loginContext
.logout();
218 HttpServletRequest httpRequest
= RWT
.getRequest();
219 HttpSession httpSession
= httpRequest
.getSession();
220 httpSession
.setAttribute(ACCESS_CONTROL_CONTEXT
, null);
221 RWT
.getRequest().getSession().setMaxInactiveInterval(1);
222 log
.info("Logged out " + (username
!= null ? username
: "")
223 + " (THREAD=" + Thread
.currentThread().getId() + ")");
224 } catch (LoginException e
) {
225 log
.error("Error when logging out", e
);