]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
Introduce Argeo 2 security model-
[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.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;
26
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;
41
42 /**
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
45 * subject.
46 */
47 public class SecureEntryPoint implements EntryPoint {
48 private final static Log log = LogFactory.getLog(SecureEntryPoint.class);
49
50 /**
51 * From org.springframework.security.context.
52 * HttpSessionContextIntegrationFilter
53 */
54 protected static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
55
56 /**
57 * How many seconds to wait before invalidating the session if the user has
58 * not yet logged in.
59 */
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;
64
65 /** Override to provide an application specific workbench advisor */
66 protected RapWorkbenchAdvisor createRapWorkbenchAdvisor(String username) {
67 return new RapWorkbenchAdvisor(username);
68 }
69
70 @Override
71 public final int createUI() {
72 // Short login timeout so that the modal dialog login doesn't hang
73 // around too long
74 RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout);
75
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);
84
85 final Display display = PlatformUI.createDisplay();
86 Subject subject = new Subject();
87
88 final LoginContext loginContext;
89 try {
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);
96 }
97
98 tryLogin: while (subject.getPrincipals(X500Principal.class).size() == 0) {
99 try {
100 loginContext.login();
101 if (subject.getPrincipals(X500Principal.class).size() == 0)
102 throw new ArgeoException("Login succeeded but no auth");// fatal
103
104 // add security context to session
105 // if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) ==
106 // null)
107 // httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
108 // SecurityContextHolder.getContext());
109
110 // add thread locale to RWT session
111 if (log.isTraceEnabled())
112 log.trace("Locale " + LocaleUtils.threadLocale.get());
113 RWT.setLocale(LocaleUtils.threadLocale.get());
114
115 // once the user is logged in, longer session timeout
116 RWT.getRequest().getSession()
117 .setMaxInactiveInterval(sessionTimeout);
118
119 if (log.isDebugEnabled())
120 log.debug("Authenticated " + subject);
121 } catch (LoginException e) {
122 BadCredentialsException bce = wasCausedByBadCredentials(e);
123 if (bce != null) {
124 MessageDialog.openInformation(display.getActiveShell(),
125 "Bad Credentials", bce.getMessage());
126 // retry login
127 continue tryLogin;
128 }
129 return processLoginDeath(display, e);
130 }
131 }
132
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() {
137 public void run() {
138 if (log.isTraceEnabled())
139 log.trace("Display disposed");
140 try {
141 loginContext.logout();
142 } catch (LoginException e) {
143 log.error("Error when logging out", e);
144 }
145 }
146 });
147
148 //
149 // RUN THE WORKBENCH
150 //
151 Integer returnCode = null;
152 try {
153 returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
154 public Integer run() {
155 RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
156 int result = PlatformUI.createAndRunWorkbench(display,
157 workbenchAdvisor);
158 return new Integer(result);
159 }
160 });
161 // Explicit exit from workbench
162 fullLogout(loginContext, username);
163 } finally {
164 display.dispose();
165 }
166 return returnCode;
167 }
168
169 private Integer processLoginDeath(Display display, LoginException e) {
170 // check thread death
171 ThreadDeath td = wasCausedByThreadDeath(e);
172 if (td != null) {
173 display.dispose();
174 throw td;
175 }
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);
180 display.dispose();
181 return -1;
182 } else {
183 throw new ArgeoException(
184 "Unexpected exception during authentication", e);
185 }
186
187 }
188
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;
193
194 if (t instanceof CredentialNotFoundException)
195 return new BadCredentialsException("Login canceled");
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 private void fullLogout(LoginContext loginContext, String username) {
218 try {
219 loginContext.logout();
220 // SecurityContextHolder.clearContext();
221
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);
230 }
231 }
232 }