Index: cumin/python/cumin/config.py
===================================================================
--- cumin/python/cumin/config.py	(revision 4794)
+++ cumin/python/cumin/config.py	(working copy)
@@ -36,6 +36,9 @@
         param = ConfigParameter(web, "port", int)
         param.default = 45672
 
+        param = ConfigParameter(web, "auth", str)
+        param.default = 'internal'
+
         param = ConfigParameter(web, "operator-email", str)
 
         param = ConfigParameter(web, "user", str)
Index: cumin/python/cumin/main.py
===================================================================
--- cumin/python/cumin/main.py	(revision 4794)
+++ cumin/python/cumin/main.py	(working copy)
@@ -21,6 +21,7 @@
 from user import *
 from util import *
 from widgets import *
+from authenticator import *
 
 from wooly import Session
 from cumin.stat import PieChartPage
@@ -31,10 +32,12 @@
 
 class Cumin(Application):
     def __init__(self, home, broker_uris, database_dsn,
-                 host="localhost", port=45672, persona="default"):
+                 host="localhost", port=45672, persona="default", 
+                 authmech=["internal"]):
         super(Cumin, self).__init__()
 
         self.home = home
+        self.authmech = authmech
 
         model_dir = os.path.join(self.home, "model")
 
@@ -43,6 +46,7 @@
         self.database = CuminDatabase(self, database_dsn)
         self.server = CuminServer(self, host, port)
         self.admin = CuminAdmin(self)
+        self.authenticator = CuminAuthenticator(self)
 
         self.add_resource_dir(os.path.join(self.home, "resources-wooly"))
         self.add_resource_dir(os.path.join(self.home, "resources"))
@@ -590,6 +594,7 @@
 
         def render_cell_content(self, session, record):
             created = self.field.get_content(session, record)
+            import time
             return fmt_duration(time.time() - secs(created))
 
         def render_text_align(self, session):
Index: cumin/python/cumin/account/widgets.py
===================================================================
--- cumin/python/cumin/account/widgets.py	(revision 4794)
+++ cumin/python/cumin/account/widgets.py	(working copy)
@@ -61,7 +61,6 @@
         def render_content(self, sessino):
             return "Change password"
 
-
 class LoginPage(HtmlPage):
     def __init__(self, app, name):
         super(LoginPage, self).__init__(app, name)
@@ -127,15 +126,16 @@
                 if not user:
                     self.login_invalid.set(session, True)
                     return
-
-                crypted = user.password
-
-                if crypted and crypt(password, crypted) == crypted:
+# hack begin
+                authenticated = self.app.authenticator.authenticate(name,password)
+#                crypted = user.password
+                if authenticated:
+#                if crypted and crypt(password, crypted) == crypted:
                     # You're in!
 
                     login = LoginSession(self.app, user)
                     session.client_session.attributes["login_session"] = login
-
+                    log.debug(session.client_session)
                     url = self.page.origin.get(session)
 
                     self.page.redirect.set(session, url)
@@ -178,21 +178,23 @@
         self.add_field(self.new1)
 
     def validate(self, session):
+        user = session.client_session.attributes["login_session"].user
+        # In case a different login session for this user has made
+        # changes, refresh the user object
+        user.load(session.cursor)
+        if len(user.password) == 0:
+            error = FormError("An external authentication method is used for "\
+                              "this user, password change ignored")
+            self.errors.add(session, error)
+            return
+
         super(ChangePasswordForm, self).validate(session)
 
         current = self.current.get(session)
         new0 = self.new0.get(session)
         new1 = self.new1.get(session)
 
-        user = session.client_session.attributes["login_session"].user
-
-        # In case a different login session for this user has made
-        # changes, refresh the user object
-
-        user.load(session.cursor)
-
         crypted = user.password
-
         if crypt_password(current, crypted) != crypted:
             error = FormError("The password is incorrect")
             self.errors.add(session, error)
Index: cumin/python/cumin/authenticator.py
===================================================================
--- cumin/python/cumin/authenticator.py	(revision 0)
+++ cumin/python/cumin/authenticator.py	(revision 0)
@@ -0,0 +1,84 @@
+from util import *
+import ldap
+import ldapurl
+
+log = logging.getLogger("cumin.authenticator")
+
+class CuminAuthenticator(object):
+
+    def __init__(self,app):
+        self.authenticators=[]
+        self.app = app
+        log.info("Initializing %s", self)
+        for authmech in app.authmech:
+            if authmech.startswith("ldap="):
+                try:
+                    log.info("Adding ldap authenticator")
+                    self.authenticators.append(CuminAuthenticatorLDAP(authmech))
+                except:
+                    log.error("Cannot make instance of %s, missing LDAP module.", authmech )
+            elif authmech.startswith("script="):
+                log.info("Adding script authenticator")
+                self.authenticators.append(CuminAuthenticatorScript(authmech))
+            elif authmech.startswith("internal"):
+                log.info("Adding internal authenticator.")
+            else:
+                log.error("Unsupported authenticator type %s", authmech)
+
+    def authenticate(self,username,password):
+        log.debug("Calling authenticate")
+        cursor = self.app.database.get_read_cursor()
+        cls = self.app.model.com_redhat_cumin.User
+        user = cls.get_object(cursor, name=username)
+        external = len(user.password) == 0
+        log.debug("is external user %s" % external)
+        if not external:
+            return crypt(password, user.password) == user.password
+        else:
+            for authenticator in self.authenticators:
+                log.debug("Trying authenticator %s", authenticator )
+                if authenticator.authenticate(username,password):
+                    return True
+            return False
+       
+class CuminAuthenticatorLDAP(object):
+
+    def __init__(self,url):
+        log.info("Initializing %s", self)
+        myurl = url.split("=",1)[1]
+        log.debug("Parsing ldap url: %s", myurl)
+        try:
+            self.url = ldapurl.LDAPUrl(myurl)
+        except Exception,e:
+            log.error("Cannot parse ldap url %s", e)
+               
+    def authenticate(self,username,password):
+        log.debug("Authenticating against %s", self)
+        try:
+            conn = ldap.initialize(self.url.urlscheme + "://" + self.url.hostport )
+        except Exception,e:
+            log.error("Cannot contact ldap server %s",e)
+            return False
+
+        if self.url.who and self.url.cred:
+            try:
+                self.conn.simple_bind_s(self.url.who,self.url.cred)
+            except:
+                log.error("Cannot authenticate as service %s",self.url.who)
+                return False
+            filter = self.url.filterstr % username
+            res = conn.search_s(self.url.dn,self.url.scope,filter)
+            for dn,ent in res:
+                try:
+                    conn.simple_bind_s(dn,password)
+                    return True
+                except:
+                    log.info("Unable to authenticate %s " , dn )
+            return False
+
+class CuminAuthenticatorScript(object):
+    def __init__(self,commandline):
+        self.commandline = commandline
+
+    def authenticate(self,username,password):
+        log.debug("Executing %s %s",(self.commandline,username))
Index: cumin/bin/cumin-admin
===================================================================
--- cumin/bin/cumin-admin	(revision 4794)
+++ cumin/bin/cumin-admin	(working copy)
@@ -94,6 +94,7 @@
     lines.append("User commands:")
     lines.append("")
     lines.append("  add-user USER [PASSWORD]      Add USER")
+    lines.append("  external-user USER            Add USER with external authentication")
     lines.append("  remove-user USER              Remove USER")
     lines.append("  add-assignment USER ROLE      Add USER to ROLE")
     lines.append("  remove-assignment USER ROLE   Remove USER from ROLE")
@@ -161,24 +162,43 @@
 
 def handle_list_users(app, cursor, opts, args):
  
-    print "  ID Name                 Roles"
-    print "---- -------------------- --------------------"
+    print "  ID   Name                 Roles"
+    print "----   -------------------- --------------------"
 
     users, roles_by_user_id = get_users(app, cursor)
-
+    externals = 0
     for user in users:
         try:
             roles = ", ".join(roles_by_user_id[user._id])
         except KeyError:
             roles = ""
+        if len(user.password) == 0:
+            ex = "*"
+            externals += 1
+        else:
+            ex = " "
+        print "%4i %s %-20s %-20s" % (user._id, ex, user.name, roles)
 
-        print "%4i %-20s %-20s" % (user._id, user.name, roles)
-
     count = len(users)
 
     print
     print "(%i user%s found)" % (count, ess(count))
+    if externals > 0:
+        print "(%i external user%s, indicated by *)" % (externals, ess(externals))
 
+def handle_external_user(app, cursor, opts, args):
+    try:
+        name = args[0]
+    except IndexError:
+        error("USER is required")
+    role = app.admin.get_role(cursor, "user")
+    try:
+        user = app.admin.add_user(cursor, name, "")
+    except IntegrityError:
+        error("A user called '%s' already exists" % name)
+    app.admin.add_assignment(cursor, user, role)
+    print "External user '%s' is added" % name
+
 def handle_add_user(app, cursor, opts, args):
     try:
         name = args[0]
@@ -189,18 +209,14 @@
         password = args[1]
     except IndexError:
         password = prompt_password()
-
     crypted = crypt_password(password)
 
     role = app.admin.get_role(cursor, "user")
-
     try:
         user = app.admin.add_user(cursor, name, crypted)
     except IntegrityError:
         error("A user called '%s' already exists" % name)
-
     app.admin.add_assignment(cursor, user, role)
-
     print "User '%s' is added" % name
 
 def handle_export_users(app, cursor, opts, args):
@@ -240,15 +256,19 @@
     for info in user_data:
         line += 1
 
+        count = 0
         for field in info:
             # Don't suppose it's possible for data to be anything other than
             # strings from csv, but this is where such type checking goes
             if type(field) != str:
                 error("Data error, line %u, fields must be strings" % line)
-            if len(field) == 0:
-                error("Data error, line %u, importer "\
-                          "does not allow empty fields" % line)
 
+            # Empty password field is external auth indicator, allow it
+            if len(field) == 0 and count != 1:
+                error("Data error, line %u, field %u, importer "\
+                          "does not allow empty field" % (line, field+1))
+            count += 1
+
         if len(info) < 2:
             error("Data error, line %u, not enough fields. "\
                   " User and password are required" % (line))
@@ -263,7 +283,7 @@
             # before we get an integrity error
             warn("A user called '%s' already exists, skipping" % info[0])
             continue
-            
+
         try:
             user = app.admin.add_user(cursor, info[0], info[1])
         except IntegrityError:
@@ -284,10 +304,12 @@
         error("USER is required")
 
     user = app.admin.get_user(cursor, name)
-    user.delete(cursor)
+    if user is None:
+        print "No such user '%s'" % name
+    else:
+        user.delete(cursor)
+        print "User '%s' is removed" % name
 
-    print "User '%s' is removed" % name
-
 def handle_list_roles(app, cursor, opts, args):
     cls = app.model.com_redhat_cumin.Role
     roles = cls.get_selection(cursor)
Index: cumin/bin/cumin-web
===================================================================
--- cumin/bin/cumin-web	(revision 4794)
+++ cumin/bin/cumin-web	(working copy)
@@ -81,9 +81,11 @@
                                       values.log_max_archives)
 
             broker_uris = [x.strip() for x in opts.brokers.split(",")]
+            authmech = [ x.strip() for x in values.auth.split(",")]
 
             cumin = Cumin(config.get_home(), broker_uris, opts.database, 
-                          opts.host, opts.port, values.persona)
+                          opts.host, opts.port, values.persona,
+                          authmech)
 
             if type(values.sasl_mech_list) == str:
                 cumin.sasl_mech_list = values.sasl_mech_list.upper()
