[Repoze-dev] LDAP results

11 views
Skip to first unread message

Mike Orr

unread,
Jan 27, 2010, 5:39:35 PM1/27/10
to repoz...@lists.repoze.org
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
LDAPAuthenticatorPlugin(
ldap_connection_factory(), "ou=People,o=example.com")
LDAPAttributesPlugin(
ldap_connection_factory())
===

For comparision, here's my existing homegrown code which should be
self-explanatory:

===
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)
try:
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,
config["ldap.server_certificates"])
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
ideas.

--
Mike Orr <slugg...@gmail.com>
_______________________________________________
Repoze-dev mailing list
Repoz...@lists.repoze.org
http://lists.repoze.org/listinfo/repoze-dev

Gustavo Narea

unread,
Jan 28, 2010, 7:20:10 PM1/28/10
to repoz...@lists.repoze.org
Hello, Mike.

Mike said:
> 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.

I'm afraid so :/

> 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.

That's in repoze.who itself, but I don't think that's a problem: It will be
available in the POST arguments anyway.


> 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.

I agree it makes sense. Possibly the best solution would be to make that
plugin optionally return the user Id. instead of the whole DN.


> 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.

Have you tried the `attributes' arguments?
http://code.gustavonarea.net/repoze.who.plugins.ldap/Using.html#repoze.who.plugins.ldap.LDAPAttributesPlugin


> 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.

Good point. That plugin could use a (boolean) environment variable (e.g.,
"repoze.who.ldap.connection_error") which would be checked when the user could
not be logged in.


> 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.

No, because they are two separate processes in repoze.who:
http://gustavonarea.net/uploads/Figure2.png

But in practice, that's perfectly possible if you put these attributes in
"repoze.who.identity".


> 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.

You could extend that plugin and override the add_metadata() method, so when
you already have the data in the session, you put it in the `identity'
variable passed as argument; if not, you would run the parent class'
add_metadata().

HTH.

PS: Please note this plugin is in bug-fix mode, not under active development,
for the time being:
http://lists.repoze.org/pipermail/repoze-dev/2010-January/002546.html
--
Gustavo Narea <xri://=Gustavo>.
| Tech blog: =Gustavo/(+blog)/tech ~ About me: =Gustavo/about |

Reply all
Reply to author
Forward
0 new messages