Fix issue with username case in LDAP
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 30 Jun 2011 20:11:15 +0000 (20:11 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 30 Jun 2011 20:11:15 +0000 (20:11 +0000)
Improve log in mechanism in RAP

git-svn-id: https://svn.argeo.org/commons/trunk@4643 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

eclipse/runtime/org.argeo.eclipse.ui/src/main/java/org/argeo/eclipse/ui/Error.java
security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap.xml
security/plugins/org.argeo.security.ui.rap/branding/default.htm [new file with mode: 0644]
security/plugins/org.argeo.security.ui.rap/branding/favicon.ico [new file with mode: 0644]
security/plugins/org.argeo.security.ui.rap/build.properties
security/plugins/org.argeo.security.ui.rap/plugin.xml
security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/AbstractLoginDialog.java
security/plugins/pom.xml
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/KeyBasedSystemExecutionService.java
security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java

index 96724adff9d3285165cb967001267ea19be0ff69..04971d15545da6dd1393677df94da8597a3a2fc3 100644 (file)
@@ -26,6 +26,11 @@ public class Error extends TitleAreaDialog {
        private final Throwable exception;
 
        public static void show(String message, Throwable e) {
+               // rethrow ThreaDeath in order to make sure that RAP will properly clean
+               // up the UI thread
+               if (e instanceof ThreadDeath)
+                       throw (ThreadDeath) e;
+               
                new Error(getDisplay().getActiveShell(), message, e).open();
        }
 
@@ -36,7 +41,11 @@ public class Error extends TitleAreaDialog {
        /** Tries to find a display */
        private static Display getDisplay() {
                try {
-                       return PlatformUI.getWorkbench().getDisplay();
+                       Display display = PlatformUI.getWorkbench().getDisplay();
+                       if (display != null)
+                               return display;
+                       else
+                               return Display.getDefault();
                } catch (Exception e) {
                        return Display.getCurrent();
                }
index 33dc554a87de4a0af7891448d5444aedd6a9ba0a..1f2117d1ae02328426bc5b31c0599b91cee5d25f 100644 (file)
                <property name="userDetailsContextMapper" ref="jcrUserDetailsContextMapper" />
        </bean>
 
+<!--   <bean id="ldapAuthenticator" -->
+<!--           class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator"> -->
+<!--           <constructor-arg ref="contextSource" /> -->
+<!--           <property name="userDnPatterns"> -->
+<!--                   <list> -->
+<!--                           <value><![CDATA[${argeo.ldap.usernameAttribute}={0},${argeo.ldap.userBase}]]></value> -->
+<!--                   </list> -->
+<!--           </property> -->
+<!--   </bean> -->
+
+       <!-- PasswordComparisonAuthenticator doesn't work with SSHA -->
        <bean id="ldapAuthenticator"
-               class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
+               class="org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userDnPatterns">
                        <list>
                                <value><![CDATA[${argeo.ldap.usernameAttribute}={0},${argeo.ldap.userBase}]]></value>
                        </list>
                </property>
+               <property name="passwordAttributeName" value="${argeo.ldap.passwordAttribute}" />
+               <property name="passwordEncoder" ref="passwordEncoder" />
        </bean>
 
-       <!-- DOESN'T WORK WITH SSHA -->
-       <!-- <bean id="passwordComparisonAuthenticator" -->
-       <!-- class="org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator"> -->
-       <!-- <constructor-arg ref="contextSource" /> -->
-       <!-- <property name="userDnPatterns"> -->
-       <!-- <list> -->
-       <!-- <value><![CDATA[${argeo.ldap.usernameAttribute}={0},${argeo.ldap.userBase}]]></value> -->
-       <!-- </list> -->
-       <!-- </property> -->
-       <!-- <property name="passwordAttributeName" value="${argeo.ldap.passwordAttribute}" 
-               /> -->
-       <!-- <property name="passwordEncoder" ref="passwordEncoder" /> -->
-       <!-- </bean> -->
-
        <!-- USER DETAILS -->
        <bean id="userAdminDao" class="org.argeo.security.ldap.ArgeoSecurityDaoLdap">
                <constructor-arg ref="contextSource" />
diff --git a/security/plugins/org.argeo.security.ui.rap/branding/default.htm b/security/plugins/org.argeo.security.ui.rap/branding/default.htm
new file mode 100644 (file)
index 0000000..67c89f4
--- /dev/null
@@ -0,0 +1,14 @@
+<html>
+<head></head>
+<body>
+<center>
+<table height="100%">
+<tr>
+       <td style="vertical-align:middle">
+               <a style="font-family:sans-serif;color:#0066CC;text-decoration:none;" href="./secureWebUi">Login...</a>
+       </td>
+</tr>
+</table>
+</center>
+</body>
+</html>
\ No newline at end of file
diff --git a/security/plugins/org.argeo.security.ui.rap/branding/favicon.ico b/security/plugins/org.argeo.security.ui.rap/branding/favicon.ico
new file mode 100644 (file)
index 0000000..213cdf7
Binary files /dev/null and b/security/plugins/org.argeo.security.ui.rap/branding/favicon.ico differ
index b840a945646338600b24eb135854324c2080206f..572b0b491b6a4d30fb460fb57c682334a4704beb 100644 (file)
@@ -1,4 +1,5 @@
 bin.includes = plugin.xml,\
-               META-INF/
+               META-INF/,\
+               branding/
 source.. = src/main/java/
 output.. = target/classes/
index 9d6135fea0de73a15c58cbdbce451e9201f54188..7e192647dff2e82f8813bd68a250a26125837274 100644 (file)
       </application>
    </extension>
 
+       <extension
+         point="org.eclipse.rap.ui.branding">
+       <branding
+                       id="org.argeo.security.ui.rap.branding"
+            servletName="secureWebUi"
+            defaultEntrypointId="org.argeo.security.ui.rap.secureEntryPoint"
+            title="Argeo Secure Web UI"
+            favicon="branding/favicon.ico"
+            body="branding/default.htm">
+       </branding>
+       </extension>
+
 </plugin>
index 4d85cc869318d78cd8d7eae7e54ea897919fcab8..8e377e09998ccdc1ec0f4f468e6d622dc201f01c 100644 (file)
@@ -7,6 +7,7 @@ import javax.security.auth.login.LoginException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
 import org.eclipse.equinox.security.auth.ILoginContext;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.rwt.RWT;
@@ -51,37 +52,60 @@ public class SecureEntryPoint implements IEntryPoint {
                final ILoginContext loginContext = SecureRapActivator
                                .createLoginContext();
                Subject subject = null;
-               tryLogin: while (subject == null) {
+               tryLogin: while (subject == null && !display.isDisposed()) {
                        try {
                                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;
-                                       }
+                               BadCredentialsException bce = wasCausedByBadCredentials(e);
+                               if (bce != null) {
+                                       MessageDialog.openInformation(display.getActiveShell(),
+                                                       "Bad Credentials", bce.getMessage());
+                                       // retry login
+                                       continue tryLogin;
+                               }
+
+                               // check thread death
+                               ThreadDeath td = wasCausedByThreadDeath(e);
+                               if (td != null) {
+                                       display.dispose();
+                                       throw td;
+                               }
+
+                               // 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;
+                               // }
+                               // }
+
+                               if (!display.isDisposed()) {
+                                       org.argeo.eclipse.ui.Error.show(
+                                                       "Unexpected exception during authentication", e);
+                                       // this was not just bad credentials or death thread
+                                       RWT.getRequest().getSession().setMaxInactiveInterval(1);
+                                       display.dispose();
+                                       return -1;
+                               } else {
+                                       throw new ArgeoException(
+                                                       "Unexpected exception during authentication", e);
                                }
-                               // this was not just bad credentials returns
-                               RWT.getRequest().getSession().setMaxInactiveInterval(1);
-                               display.dispose();
-                               return -1;
                        }
                }
 
@@ -99,13 +123,6 @@ public class SecureEntryPoint implements IEntryPoint {
                        public void run() {
                                log.debug("Display disposed");
                                logout(loginContext, username);
-                               // invalidate session
-                               //RWT.getRequest().getSession().setMaxInactiveInterval(1);
-                               try {
-                                       Thread.sleep(2000);
-                               } catch (InterruptedException e1) {
-                                       // silent
-                               }
                        }
                });
 
@@ -121,6 +138,31 @@ public class SecureEntryPoint implements IEntryPoint {
                return processReturnCode(returnCode);
        }
 
+       /** Recursively look for {@link BadCredentialsException} in the root causes. */
+       private BadCredentialsException wasCausedByBadCredentials(Throwable t) {
+               if (t instanceof BadCredentialsException)
+                       return (BadCredentialsException) t;
+
+               if (t.getCause() != null)
+                       return wasCausedByBadCredentials(t.getCause());
+               else
+                       return null;
+       }
+
+       /**
+        * If there is a {@link ThreadDeath} in the root causes, rethrow it
+        * (important for RAP cleaning mechanism)
+        */
+       protected ThreadDeath wasCausedByThreadDeath(Throwable t) {
+               if (t instanceof ThreadDeath)
+                       return (ThreadDeath) t;
+
+               if (t.getCause() != null)
+                       return wasCausedByThreadDeath(t.getCause());
+               else
+                       return null;
+       }
+
        protected void logout(ILoginContext secureContext, String username) {
                try {
                        secureContext.logout();
index 92ca3d85de5aabe190cfb6720f6f87b82e2355f4..fecb80afc9107ecc46c2c440b61f4ee5669d112a 100644 (file)
@@ -124,10 +124,10 @@ public abstract class AbstractLoginDialog extends TrayDialog implements
                                                // 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();
-                                                       }
+                                                       // if (display.isDisposed()) {
+                                                       // log.warn("Display is disposed, killing login dialog thread");
+                                                       // throw new ThreadDeath();
+                                                       // }
                                                } catch (final Exception e) {
                                                        // do nothing
                                                }
@@ -147,6 +147,7 @@ public abstract class AbstractLoginDialog extends TrayDialog implements
                        }, true, new NullProgressMonitor(), Display.getDefault());
                } catch (ThreadDeath e) {
                        isCancelled = true;
+                       log.debug("Thread " + Thread.currentThread().getId() + " died");
                        throw e;
                } catch (Exception e) {
                        isCancelled = true;
@@ -157,12 +158,12 @@ public abstract class AbstractLoginDialog extends TrayDialog implements
                } 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
-                       }
+                       // try {
+                       // // wait for the modal context thread to gracefully exit
+                       // modalContextThread.join();
+                       // } catch (InterruptedException ie) {
+                       // // silent
+                       // }
                        modalContextThread = null;
                }
        }
index b24f6fc270f82ca976d48670ffaa8f68f2db51fa..77589310870129f09e7fbd307ddc320765a0308a 100644 (file)
@@ -27,6 +27,7 @@
                                <includes>
                                        <include>plugin.xml</include>
                                        <include>META-INF/**</include>
+                                       <include>branding/**</include>
                                        <include>jaas/**</include>
                                        <include>icons/**</include>
                                </includes>
index 07ae046539c93ca5dd8f2deddf9c6ee62db4b7c6..b5791c587f00ec2dd9ea021c4c06172cf3be7527 100644 (file)
@@ -17,6 +17,8 @@ public class KeyBasedSystemExecutionService extends AbstractSystemExecution
        public void execute(Runnable runnable) {
                try {
                        wrapWithSystemAuthentication(Executors.callable(runnable)).call();
+               } catch (RuntimeException e) {
+                       throw e;
                } catch (Exception e) {
                        throw new ArgeoException(
                                        "Exception when running system authenticated task", e);
index 5c6a88585ab4c4403fd251eff6edd72b3fa15cbe..537e01763c8a0507bdd4c8e60236b212694cb6c1 100644 (file)
@@ -22,6 +22,7 @@ import org.argeo.jcr.JcrUtils;
 import org.argeo.security.jcr.JcrUserDetails;
 import org.springframework.ldap.core.DirContextAdapter;
 import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.security.BadCredentialsException;
 import org.springframework.security.GrantedAuthority;
 import org.springframework.security.context.SecurityContextHolder;
 import org.springframework.security.providers.encoding.PasswordEncoder;
@@ -108,13 +109,25 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
 
        /** @return path to the user home node */
        protected String mapLdapToJcr(String username, DirContextOperations ctx) {
+               String usernameLdap = ctx.getStringAttribute(usernameAttribute);
+               // log.debug("username=" + username + ", usernameLdap=" + usernameLdap);
+               if (!username.equals(usernameLdap)) {
+                       String msg = "Provided username '" + username
+                                       + "' is different from username stored in LDAP '"
+                                       + usernameLdap+"'";
+                       // we log it because the exception may not be displayed
+                       log.error(msg);
+                       throw new BadCredentialsException(msg);
+               }
+
                try {
+
                        Node userHome = JcrUtils.getUserHome(session, username);
                        if (userHome == null)
                                userHome = JcrUtils.createUserHome(session, homeBasePath,
                                                username);
                        String userHomePath = userHome.getPath();
-                       Node userProfile;  // = userHome.getNode(ARGEO_PROFILE);
+                       Node userProfile; // = userHome.getNode(ARGEO_PROFILE);
                        if (userHome.hasNode(ARGEO_PROFILE)) {
                                userProfile = userHome.getNode(ARGEO_PROFILE);
                        } else {