I took the plunge this week and installed Repoze.who/what and made a
Pylons site using htpasswd and LDAP. It's all working but I'm
surprised a bit at the LDAP behavior compared to the homegrown
authenticator I've been using, and wondering if I'll need to write my
own adapter to get the behavior I want.

I'm using LDAPAuthenticatorPlugin and LDAPAttributesPlugin, with a
custom connection:

def ldap_connection_factory():
    ldap_url = "ldaps://example.com:636"
    certs = os.path.join(config["here"], "myapp", "lib", "certs.pem")
    ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, certs)
    server = ldap.initialize(ldap_url, trace_level=2)
    server.protocol = ldap.VERSION3
    return server
        ldap_connection_factory(), "ou=People,o=example.com")

For comparision, here's my existing homegrown code which should be

def authenticate_ldap(username, password):
    """Authenticate a user via LDAP and return his/her LDAP properties.

    Raises AuthenticationError if the credentials are rejected, or
    EnvironmentError if the LDAP server can't be reached.
    import ldap
    if "," in username:
        raise UsernameError("invalid character in username: ,")
    uid = chop_at(username, "@example.com")
    dn = "uid=%s,ou=People,o=example.com" % uid
    log.debug("Authenticating %r at %s", dn, LDAP_SERVER)
        server = ldap.initialize(LDAP_SERVER)
        server.protocol = ldap.VERSION3
        server.simple_bind_s(dn, password)
        properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
        if not properties:
            raise ldap.NO_SUCH_OBJECT()
    except ldap.NO_SUCH_OBJECT, e:
        log.debug("LDAP says no such user '%s' (%s)", uid, username)
        raise UsernameError()
    except ldap.INVALID_CREDENTIALS, e:
        log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
        raise PasswordError()
    except ldap.SERVER_DOWN, e:
        raise EnvironmentError("can't access authentication server")
    return properties

With this configuration I'm getting the following:

1) The plugins expect a long-lasting connection, and don't have any
provision to reconnect if the server goes down. I know our LDAP server
goes down occasionally, and I wonder if I'd have to restart the
application in that case. My code makes a separate connection for
every login, which is the other extreme. The ideal would be for the
constructor to accept a callable that creates a connection, then reuse
the existing connection if possible, or if certain exceptions occur,
create a new connnection. But it looks like I'd have to rewrite the
plugin for that.

2) The password is put into "repoze.who.identify" after a successful
login. That seems insecure. The rest of the application doesn't need
to know what the password is. I could make a metadata provider to
delete it, but that seems like a kludge.

3) The full DSN is appearing instead of the username in
"repoze.who.credentials", "repoze.who.identity['repoze.who.userid'],
and "REMOTE_USER".  I suppose that's correct from LDAP's perspective
because a username could be ambiguous, but from my perspective the DSN
is just an internal detail of LDAP, and what I need is the username to
do authorization with or display.

4) The attributes plugin is not returning the 'ou' and 'ou1'
properties which I need for authorization.  It's returning only a
small subset of the LDAP properties. It seems to be returning the ones
accessible to anonymous users. My code does that if I don't do
.simple_bind_s as the user.  But the plugin is doing the binding, so I
don't know why the properties aren't showing up.

5) The plugin doesn't differentiate between "server down", "no such
user", and "bad password", it just returns None for all of them. This
makes it impossible to give the user a specific error message.  I
could make a plugin that puts the info "repoze.who.identity", although
I think that's supposed to be blank if the login is unsuccessful. But
I don't want to tell the user they don't exist when the real problem
is the authentication server being down, or people will think their
account disappeared.

6) Is it possible for a plugin to combine authentication and metadata
in one step? Because if it's in a database, I can get both with a
single query, or if it's in LDAP I can do one right after the other in
the same connection.

7) It seems to be rechecking the metadata on every visit after they
log in, though that could be because my test app is unsophisticated
and is not saving the metadata in the session. Although if I did do
that, I'm not sure how to tell Repoze.who that I already have the
identity and don't need it to refetch the metadata.

Anyway, I'm going to mull over what to do about these things, but I
was wondering if somebody with better Repoze knowledge might have some

Mike Orr <sluggos...@gmail.com>
Repoze-dev mailing list

Reply via email to