]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
Remove JcrSecurityModel from supported APIs
[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.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpSession;
27
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;
45
46 /**
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
49 * subject.
50 */
51 public class SecureEntryPoint implements EntryPoint {
52 private final static Log log = LogFactory.getLog(SecureEntryPoint.class);
53
54 /**
55 * From org.springframework.security.context.
56 * HttpSessionContextIntegrationFilter
57 */
58 protected static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
59
60 /**
61 * How many seconds to wait before invalidating the session if the user has
62 * not yet logged in.
63 */
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;
68
69 /** Override to provide an application specific workbench advisor */
70 protected RapWorkbenchAdvisor createRapWorkbenchAdvisor(String username) {
71 return new RapWorkbenchAdvisor(username);
72 }
73
74 @Override
75 public final int createUI() {
76 // Short login timeout so that the modal dialog login doesn't hang
77 // around too long
78 RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout);
79
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)
86 SecurityContextHolder
87 .setContext((SecurityContext) contextFromSessionObject);
88
89 // if (log.isDebugEnabled())
90 // log.debug("THREAD=" + Thread.currentThread().getId()
91 // + ", sessionStore=" + RWT.getSessionStore().getId()
92 // + ", remote user=" + httpRequest.getRemoteUser());
93
94 // create display
95 final Display display = PlatformUI.createDisplay();
96 Subject subject = new Subject();
97
98 // log in
99 // Thread.currentThread().setContextClassLoader(
100 // getClass().getClassLoader());
101 final LoginContext loginContext;
102 try {
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);
109 }
110
111 tryLogin: while (subject.getPrincipals(Authentication.class).size() == 0) {
112 try {
113 loginContext.login();
114 // if () {
115 // throw new ArgeoException("Login failed");
116 // }
117
118 if (subject.getPrincipals(Authentication.class).size() == 0)
119 throw new ArgeoException("Login succeeded but no auth");// fatal
120
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());
129
130 // Once the user is logged in, longer session timeout
131 RWT.getRequest().getSession()
132 .setMaxInactiveInterval(sessionTimeout);
133
134 if (log.isDebugEnabled())
135 log.debug("Authenticated " + subject);
136 } catch (LoginException e) {
137 BadCredentialsException bce = wasCausedByBadCredentials(e);
138 if (bce != null) {
139 MessageDialog.openInformation(display.getActiveShell(),
140 "Bad Credentials", bce.getMessage());
141 // retry login
142 continue tryLogin;
143 }
144 return processLoginDeath(display, e);
145 }
146 }
147
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() {
152 public void run() {
153 if (log.isTraceEnabled())
154 log.trace("Display disposed");
155 try {
156 loginContext.logout();
157 } catch (LoginException e) {
158 log.error("Error when logging out", e);
159 }
160 }
161 });
162
163 //
164 // RUN THE WORKBENCH
165 //
166 Integer returnCode = null;
167 try {
168 returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
169 public Integer run() {
170 RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
171 int result = PlatformUI.createAndRunWorkbench(display,
172 workbenchAdvisor);
173 return new Integer(result);
174 }
175 });
176 // Explicit exit from workbench
177 fullLogout(loginContext, username);
178 } finally {
179 display.dispose();
180 }
181 return returnCode;
182 }
183
184 private Integer processLoginDeath(Display display, LoginException e) {
185 // check thread death
186 ThreadDeath td = wasCausedByThreadDeath(e);
187 if (td != null) {
188 display.dispose();
189 throw td;
190 }
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);
195 display.dispose();
196 return -1;
197 } else {
198 throw new ArgeoException(
199 "Unexpected exception during authentication", e);
200 }
201
202 }
203
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;
208
209 if (t instanceof CredentialNotFoundException)
210 return new BadCredentialsException("Login canceled");
211
212 if (t.getCause() != null)
213 return wasCausedByBadCredentials(t.getCause());
214 else
215 return null;
216 }
217
218 /**
219 * If there is a {@link ThreadDeath} in the root causes, rethrow it
220 * (important for RAP cleaning mechanism)
221 */
222 protected ThreadDeath wasCausedByThreadDeath(Throwable t) {
223 if (t instanceof ThreadDeath)
224 return (ThreadDeath) t;
225
226 if (t.getCause() != null)
227 return wasCausedByThreadDeath(t.getCause());
228 else
229 return null;
230 }
231
232 private void fullLogout(LoginContext loginContext, String username) {
233 try {
234 loginContext.logout();
235 SecurityContextHolder.clearContext();
236
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);
245 }
246 }
247 }