import org.argeo.security.SiteAuthenticationToken;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
+import org.springframework.security.BadCredentialsException;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.jaas.SecurityContextLoginModule;
private CallbackHandler callbackHandler;
private Subject subject;
+
+ private Long waitBetweenFailedLoginAttempts = 5*1000l;
public SpringLoginModule() {
}
public boolean login() throws LoginException {
- // try to retrieve Authentication from Subject
- // Set<Authentication> auths =
- // subject.getPrincipals(Authentication.class);
- // if (auths.size() > 0)
- // SecurityContextHolder.getContext().setAuthentication(
- // auths.iterator().next());
-
- // thread already logged in
- if (SecurityContextHolder.getContext().getAuthentication() != null)
- return super.login();
-
- // reset all principals and credentials
- if (log.isTraceEnabled())
- log.trace("Resetting all principals and credentials of " + subject);
- if (subject.getPrincipals() != null)
- subject.getPrincipals().clear();
- if (subject.getPrivateCredentials() != null)
- subject.getPrivateCredentials().clear();
- if (subject.getPublicCredentials() != null)
- subject.getPublicCredentials().clear();
-
- // ask for username and password
- Callback label = new TextOutputCallback(TextOutputCallback.INFORMATION,
- "Required login");
- NameCallback nameCallback = new NameCallback("User");
- PasswordCallback passwordCallback = new PasswordCallback("Password",
- false);
-
- // NameCallback urlCallback = new NameCallback("Site URL");
-
- if (callbackHandler == null) {
- throw new LoginException("No call back handler available");
- // return false;
- }
try {
+ // thread already logged in
+ if (SecurityContextHolder.getContext().getAuthentication() != null)
+ return super.login();
+
+ // reset all principals and credentials
+ if (log.isTraceEnabled())
+ log.trace("Resetting all principals and credentials of "
+ + subject);
+ if (subject.getPrincipals() != null)
+ subject.getPrincipals().clear();
+ if (subject.getPrivateCredentials() != null)
+ subject.getPrivateCredentials().clear();
+ if (subject.getPublicCredentials() != null)
+ subject.getPublicCredentials().clear();
+
+ // ask for username and password
+ Callback label = new TextOutputCallback(
+ TextOutputCallback.INFORMATION, "Required login");
+ NameCallback nameCallback = new NameCallback("User");
+ PasswordCallback passwordCallback = new PasswordCallback(
+ "Password", false);
+
+ // NameCallback urlCallback = new NameCallback("Site URL");
+
+ if (callbackHandler == null)
+ throw new LoginException("No call back handler available");
callbackHandler.handle(new Callback[] { label, nameCallback,
passwordCallback });
- } catch (Exception e) {
- throw new RuntimeException("Unexpected exception when handling", e);
- }
- // Set user name and password
- String username = nameCallback.getName();
- String password = "";
- if (passwordCallback.getPassword() != null) {
- password = String.valueOf(passwordCallback.getPassword());
+ // Set user name and password
+ String username = nameCallback.getName();
+ if (username == null || username.trim().equals(""))
+ return false;
+
+ String password = "";
+ if (passwordCallback.getPassword() != null)
+ password = String.valueOf(passwordCallback.getPassword());
+
+ // String url = urlCallback.getName();
+ // TODO: set it via system properties
+ String workspace = null;
+
+ SiteAuthenticationToken credentials = new SiteAuthenticationToken(
+ username, password, null, workspace);
+
+ Authentication authentication;
+ try {
+ authentication = authenticationManager
+ .authenticate(credentials);
+ } catch (BadCredentialsException e) {
+ // wait between failed login attempts
+ Thread.sleep(waitBetweenFailedLoginAttempts);
+ throw e;
+ }
+ registerAuthentication(authentication);
+ boolean res = super.login();
+ return res;
+ } catch (LoginException e) {
+ throw e;
+ } catch (ThreadDeath e) {
+ LoginException le = new LoginException(
+ "Spring Security login thread died");
+ le.initCause(e);
+ throw le;
+ } catch (Exception e) {
+ LoginException le = new LoginException(
+ "Spring Security login failed");
+ le.initCause(e);
+ throw le;
}
-
- // String url = urlCallback.getName();
- // TODO: set it via system properties
- String workspace = null;
-
- SiteAuthenticationToken credentials = new SiteAuthenticationToken(
- username, password, null, workspace);
-
- // try {
- Authentication authentication = authenticationManager
- .authenticate(credentials);
- registerAuthentication(authentication);
- boolean res = super.login();
- return res;
- // } catch (BadCredentialsException bce) {
- // throw bce;
- // } catch (LoginException e) {
- // // LoginException loginException = new LoginException(
- // // "Bad credentials");
- // // loginException.initCause(e);
- // throw e;
- // }
}
@Override
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.equinox.security.auth.ILoginContext;
-import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.rwt.RWT;
import org.eclipse.rwt.lifecycle.IEntryPoint;
-import org.eclipse.rwt.service.SessionStoreEvent;
-import org.eclipse.rwt.service.SessionStoreListener;
-import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
import org.eclipse.ui.application.WorkbenchAdvisor;
import org.eclipse.ui.application.WorkbenchWindowAdvisor;
+import org.springframework.security.BadCredentialsException;
-public class SecureEntryPoint implements IEntryPoint, SessionStoreListener {
+/**
+ * RAP entry point with login capabilities. On the user has been authenticated,
+ * the workbench is run as a privileged action by the related subject.
+ */
+public class SecureEntryPoint implements IEntryPoint {
private final static Log log = LogFactory.getLog(SecureEntryPoint.class);
- @SuppressWarnings("unchecked")
+ /**
+ * How many seconds to wait before invalidating the session if the user has
+ * not yet logged in.
+ */
+ private Integer loginTimeout = 1 * 60;
+ private Integer sessionTimeout = 15 * 60;
+
@Override
public int createUI() {
- // 15 mins session timeout
- RWT.getRequest().getSession().setMaxInactiveInterval(15 * 60);
+ // Short login timeout so that the modal dialog login doesn't hang
+ // around too long
+ RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout);
if (log.isDebugEnabled())
log.debug("THREAD=" + Thread.currentThread().getId()
+ ", sessionStore=" + RWT.getSessionStore().getId());
- final ILoginContext loginContext = SecureRapActivator
- .createLoginContext();
Integer returnCode = null;
+
+ // create display
Display display = PlatformUI.createDisplay();
+ // log in
+ final ILoginContext loginContext = SecureRapActivator
+ .createLoginContext();
Subject subject = null;
- try {
- loginContext.login();
- subject = loginContext.getSubject();
- } catch (LoginException e) {
- log.error("Error when logging in.", e);
- MessageDialog.openInformation(display.getActiveShell(),
- "Login failed", "Login failed");
- display.dispose();
- RWT.getRequest().getSession().setMaxInactiveInterval(1);
+ tryLogin: while (subject == null) {
try {
- Thread.sleep(2000);
- } catch (InterruptedException e1) {
- // silent
+ loginContext.login();
+ subject = loginContext.getSubject();
+ } catch (LoginException e) {
+ if (e.getCause() != null) {
+ Throwable firstCause = e.getCause();
+ // log.error("Cause", firstCause);
+ if (firstCause instanceof LoginException
+ && firstCause.getCause() != null) {
+ Throwable secondCause = firstCause.getCause();
+ if (secondCause instanceof BadCredentialsException) {
+ MessageDialog.openInformation(
+ display.getActiveShell(),
+ "Bad Credentials",
+ "Your credentials are incorrect");
+ // retry login
+ continue tryLogin;
+ } else if (secondCause instanceof ThreadDeath) {
+ // rethrow thread death caused by dialog UI timeout
+ throw (ThreadDeath) secondCause;
+ }
+
+ } else if (firstCause instanceof ThreadDeath) {
+ throw (ThreadDeath) firstCause;
+ }
+ }
+ // this was not just bad credentials returns
+ RWT.getRequest().getSession().setMaxInactiveInterval(1);
+ display.dispose();
+ return -1;
}
- // throw new RuntimeException("Login failed", e);
- return -1;
}
// identify after successful login
if (log.isDebugEnabled())
- log.debug("subject=" + subject);
+ log.debug("Authenticated " + subject);
final String username = subject.getPrincipals().iterator().next()
.getName();
- if (log.isDebugEnabled())
- log.debug(username + " logged in");
+
+ // Once the user is logged in, she can have a longer session timeout
+ RWT.getRequest().getSession().setMaxInactiveInterval(sessionTimeout);
+
+ // Logout callback when the display is disposed
display.disposeExec(new Runnable() {
public void run() {
log.debug("Display disposed");
logout(loginContext, username);
// invalidate session
- RWT.getRequest().getSession().setMaxInactiveInterval(1);
+ //RWT.getRequest().getSession().setMaxInactiveInterval(1);
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
}
});
+ //
+ // RUN THE WORKBENCH
+ //
try {
returnCode = (Integer) Subject.doAs(subject, getRunAction(display));
- loginContext.logout();
- return processReturnCode(returnCode);
- } catch (Exception e) {
- if (subject != null)
- logout(loginContext, username);
- // RWT.getRequest().getSession().setMaxInactiveInterval(1);
- log.error("Unexpected error", e);
- // throw new ArgeoException("Cannot login", e);
+ logout(loginContext, username);
} finally {
display.dispose();
}
- return -1;
+ return processReturnCode(returnCode);
}
- static void logout(ILoginContext secureContext, String username) {
+ protected void logout(ILoginContext secureContext, String username) {
try {
secureContext.logout();
log.info("Logged out " + (username != null ? username : "")
}
}
- // static void closeWorkbench() {
- // final IWorkbench workbench;
- // try {
- // workbench = PlatformUI.getWorkbench();
- // } catch (Exception e) {
- // return;
- // }
- // if (workbench == null)
- // return;
- // final Display display = workbench.getDisplay();
- // if (display != null && !display.isDisposed())
- // display.syncExec(new Runnable() {
- //
- // public void run() {
- // if (!display.isDisposed())
- // workbench.close();
- // }
- // });
- //
- // if (log.isDebugEnabled())
- // log.debug("Workbench closed");
- // }
-
- static class FailedLogin extends MessageDialog {
-
- public FailedLogin(Shell parentShell, String dialogTitle,
- Image dialogTitleImage, String dialogMessage,
- int dialogImageType, String[] dialogButtonLabels,
- int defaultIndex) {
- super(parentShell, "Failed ", dialogTitleImage, dialogMessage,
- dialogImageType, dialogButtonLabels, defaultIndex);
- // TODO Auto-generated constructor stub
- }
-
- }
-
@SuppressWarnings("rawtypes")
private PrivilegedAction getRunAction(final Display display) {
return new PrivilegedAction() {
};
}
+ /** To be overridden */
protected Integer createAndRunWorkbench(Display display) {
return PlatformUI.createAndRunWorkbench(display,
createWorkbenchAdvisor());
}
+ /** To be overridden */
protected Integer processReturnCode(Integer returnCode) {
return returnCode;
}
+ /** To be overridden */
protected WorkbenchAdvisor createWorkbenchAdvisor() {
return new SecureWorkbenchAdvisor() {
public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
};
}
-
- @Override
- public void beforeDestroy(SessionStoreEvent event) {
- if (log.isDebugEnabled())
- log.debug("RWT session " + event.getSessionStore().getId()
- + " about to be destroyed. THREAD="
- + Thread.currentThread().getId());
-
- }
-
}
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.security.ui.SecurityUiPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
+/** Base for login dialogs */
public abstract class AbstractLoginDialog extends TrayDialog implements
CallbackHandler {
+ private final static Log log = LogFactory.getLog(AbstractLoginDialog.class);
+
+ private Thread modalContextThread = null;
boolean processCallbacks = false;
boolean isCancelled = false;
Callback[] callbackArray;
* .callback.Callback[])
*/
public void handle(final Callback[] callbacks) throws IOException {
+ // clean previous usage
+ if (processCallbacks) {
+ // this handler was already used
+ processCallbacks = false;
+ }
+
+ if (modalContextThread != null) {
+ try {
+ modalContextThread.join(1000);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ modalContextThread = null;
+ }
+
+ // initialize
this.callbackArray = callbacks;
final Display display = Display.getDefault();
display.syncExec(new Runnable() {
ModalContext.run(new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) {
+ modalContextThread = Thread.currentThread();
// Wait here until OK or cancel is pressed, then let it rip.
// The event
// listener
// is responsible for closing the dialog (in the
// loginSucceeded
// event).
- while (!processCallbacks) {
+ while (!processCallbacks && (modalContextThread != null)
+ && (modalContextThread == Thread.currentThread())
+ && SecurityUiPlugin.getDefault() != null) {
+ // Note: SecurityUiPlugin.getDefault() != null is false
+ // when the OSGi runtime is shut down
try {
Thread.sleep(100);
+ if (display.isDisposed()) {
+ log.warn("Display is disposed, killing login dialog thread");
+ throw new ThreadDeath();
+ }
} catch (final Exception e) {
// do nothing
}
((NameCallback) callback).setName(null);
}
}, true, new NullProgressMonitor(), Display.getDefault());
- } catch (final Exception e) {
- final IOException ioe = new IOException();
+ } catch (ThreadDeath e) {
+ isCancelled = true;
+ throw e;
+ } catch (Exception e) {
+ isCancelled = true;
+ IOException ioe = new IOException(
+ "Unexpected issue in login dialog, see root cause for more details");
ioe.initCause(e);
throw ioe;
+ } finally {
+ // so that the modal thread dies
+ processCallbacks = true;
+ try {
+ // wait for the modal context thread to gracefully exit
+ modalContextThread.join(1000);
+ } catch (InterruptedException ie) {
+ // silent
+ }
+ modalContextThread = null;
}
}