]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
c176ba62b59fc4d4e13372cfc50e870a140b2046
[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.AccessControlContext;
19 import java.security.AccessController;
20 import java.security.PrivilegedAction;
21
22 import javax.security.auth.Subject;
23 import javax.security.auth.callback.CallbackHandler;
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;
30
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.widgets.auth.DefaultLoginDialog;
36 import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
37 import org.argeo.util.LocaleUtils;
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;
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 final static String ACCESS_CONTROL_CONTEXT = "org.argeo.node.accessControlContext";
51 private final static Log log = LogFactory.getLog(SecureEntryPoint.class);
52
53 /**
54 * How many seconds to wait before invalidating the session if the user has
55 * not yet logged in.
56 */
57 private Integer loginTimeout = 1 * 60;
58 // TODO make it configurable
59 /** Default session timeout is 8 hours (European working day length) */
60 private Integer sessionTimeout = 8 * 60 * 60;
61
62 /** Override to provide an application specific workbench advisor */
63 protected RapWorkbenchAdvisor createRapWorkbenchAdvisor(String username) {
64 return new RapWorkbenchAdvisor(username);
65 }
66
67 @Override
68 public final int createUI() {
69 // Short login timeout so that the modal dialog login doesn't hang
70 // around too long
71 RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout);
72
73 final Display display = PlatformUI.createDisplay();
74
75 // load context from session
76 HttpServletRequest httpRequest = RWT.getRequest();
77 final HttpSession httpSession = httpRequest.getSession();
78 AccessControlContext acc = (AccessControlContext) httpSession
79 .getAttribute(ACCESS_CONTROL_CONTEXT);
80
81 final Subject subject;
82 if (acc != null
83 && Subject.getSubject(acc).getPrincipals(X500Principal.class)
84 .size() == 1) {
85 subject = Subject.getSubject(acc);
86 } else {
87 subject = new Subject();
88
89 final LoginContext loginContext;
90 try {
91 CallbackHandler callbackHandler = new DefaultLoginDialog(
92 display.getActiveShell());
93 loginContext = new LoginContext(
94 AuthConstants.LOGIN_CONTEXT_USER, subject,
95 callbackHandler);
96 } catch (LoginException e1) {
97 throw new ArgeoException("Cannot initialize login context", e1);
98 }
99
100 tryLogin: while (subject.getPrincipals(X500Principal.class).size() == 0) {
101 try {
102 loginContext.login();
103 if (subject.getPrincipals(X500Principal.class).size() == 0)
104 throw new ArgeoException("Login succeeded but no auth");// fatal
105
106 // add thread locale to RWT session
107 if (log.isTraceEnabled())
108 log.trace("Locale " + LocaleUtils.threadLocale.get());
109 RWT.setLocale(LocaleUtils.threadLocale.get());
110
111 // once the user is logged in, longer session timeout
112 RWT.getRequest().getSession()
113 .setMaxInactiveInterval(sessionTimeout);
114
115 if (log.isDebugEnabled())
116 log.debug("Authenticated " + subject);
117 } catch (FailedLoginException e) {
118 MessageDialog.openInformation(display.getActiveShell(),
119 "Bad Credentials", e.getMessage());
120 // retry login
121 continue tryLogin;
122 } catch (LoginException e) {
123 return processLoginDeath(display, e);
124 }
125 }
126 }
127 final String username = subject.getPrincipals(X500Principal.class)
128 .iterator().next().getName();
129 // Logout callback when the display is disposed
130 display.disposeExec(new Runnable() {
131 public void run() {
132 if (log.isTraceEnabled())
133 log.trace("Display disposed");
134 try {
135 LoginContext loginContext = new LoginContext(
136 AuthConstants.LOGIN_CONTEXT_USER, subject);
137 loginContext.logout();
138 } catch (LoginException e) {
139 log.error("Error when logging out", e);
140 }
141 }
142 });
143
144 //
145 // RUN THE WORKBENCH
146 //
147 Integer returnCode = null;
148 try {
149 returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
150 public Integer run() {
151 // add security context to session
152 httpSession.setAttribute(ACCESS_CONTROL_CONTEXT,
153 AccessController.getContext());
154
155 // start workbench
156 RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
157 int result = PlatformUI.createAndRunWorkbench(display,
158 workbenchAdvisor);
159 return new Integer(result);
160 }
161 });
162 // Explicit exit from workbench
163 fullLogout(subject, username);
164 } finally {
165 display.dispose();
166 }
167 return returnCode;
168 }
169
170 private Integer processLoginDeath(Display display, LoginException e) {
171 // check thread death
172 ThreadDeath td = wasCausedByThreadDeath(e);
173 if (td != null) {
174 display.dispose();
175 throw td;
176 }
177 if (!display.isDisposed()) {
178 ErrorFeedback.show("Unexpected exception during authentication", e);
179 // this was not just bad credentials or death thread
180 RWT.getRequest().getSession().setMaxInactiveInterval(1);
181 display.dispose();
182 return -1;
183 } else {
184 throw new ArgeoException(
185 "Unexpected exception during authentication", e);
186 }
187
188 }
189
190 /**
191 * If there is a {@link ThreadDeath} in the root causes, rethrow it
192 * (important for RAP cleaning mechanism)
193 */
194 protected ThreadDeath wasCausedByThreadDeath(Throwable t) {
195 if (t instanceof ThreadDeath)
196 return (ThreadDeath) t;
197
198 if (t.getCause() != null)
199 return wasCausedByThreadDeath(t.getCause());
200 else
201 return null;
202 }
203
204 private void fullLogout(Subject subject, String username) {
205 try {
206 LoginContext loginContext = new LoginContext(
207 AuthConstants.LOGIN_CONTEXT_USER, subject);
208 loginContext.logout();
209 HttpServletRequest httpRequest = RWT.getRequest();
210 HttpSession httpSession = httpRequest.getSession();
211 httpSession.setAttribute(ACCESS_CONTROL_CONTEXT, null);
212 RWT.getRequest().getSession().setMaxInactiveInterval(1);
213 log.info("Logged out " + (username != null ? username : "")
214 + " (THREAD=" + Thread.currentThread().getId() + ")");
215 } catch (LoginException e) {
216 log.error("Error when logging out", e);
217 }
218 }
219 }