Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

mateuszmandera
Copy link

@mateuszmandera mateuszmandera commented Sep 1, 2019

Example use:

email_search = LDAPReverseEmailSearch(LDAPBackend(), "[email protected]")
result_users = email_search.search_for_users(should_populate=True)

more extensive examples in tests.

If I can get some feedback whether this is mergeable in this form, or if I should make some changes, I'll add documentation too.

We need this in https://github.com/zulip/zulip to, during authentication, be able to check if an email belongs to ldap. This feature should be useful to users in general though, so I'm submitting as a PR.

Having the connection-handling logic in a separate class allows more
extensibility.
@mateuszmandera mateuszmandera force-pushed the master branch 2 times, most recently from 7555cb3 to e26d0ef Compare September 1, 2019 03:36
@timabbott
Copy link

timabbott commented Sep 3, 2019

To explain the use case a bit more, for Zulip servers that are using LDAP authentication for most accounts but also managing some accounts using Zulip's built-in email/password authentication, we would really like to enforce the rule that "if an email address has an LDAP account, it can't login using email/password". For that, we need a way to do in LDAP email -> (account data) lookups, not just the username -> (account data) lookups that are typically done for LDAP authentication.

We're happy to put active work into improving this PR and/or helping maintain this feature should upstream be interested in merging this. .

(And thanks for a great library!)

@timabbott
Copy link

Just wanted to do a quick ping on this; I'd particularly love feedback on "if django-auth-ldap were to add this feature, is this what you'd call the setting?", since it'll be somewhat annoying for our users if we ship a version of this from a temporary fork with a slightly different version of the setting name.

@dwasyl
Copy link

dwasyl commented Aug 17, 2020

This would definitely be a useful feature for any of our apps that have dual authentication. It'd also be the basis of a feature I'm hoping to work on to be able to prepopulate a user from the AD (in advance of their first login).

I guess nothing ever happened with this PR?

@timabbott
Copy link

I would love to see it integrated and we'd be happy to help do so; I think the project's maintainer has been busy as it hasn't been reviewed, even for "is this the right interface".

@francoisfreitag
Copy link
Member

Hi, thanks for the suggestion and working on it.

Can you explain why this feature should live in django-auth-ldap?
The project purpose is to provide an authentication backend for Django against an LDAP service.

This feature suggests offering the capability to query the LDAP for the application benefit. Why not use python-ldap or ldap3 to query to the LDAP directly?

we would really like to enforce the rule that "if an email address has an LDAP account, it can't [authenticate?]"

I’m not sure that rule would be shared by all users. Perhaps others would prefer their application first look up in the database, and then default to LDAP authentication, e.g. if only superusers are created in the database, the “regular” users living in the LDAP.

@timabbott
Copy link

@francoisfreitag thanks for the thoughtful reply!

The first commit, which refactors the _LDAPUser class via the _LDAPConnectionMixin class, so that it's possible for a project using django-auth-ldap to use django-auth-ldap logic for connecting to LDAP using its own settings. It makes sense for django-auth-ldap to provide an interface for accessing the LDAP connection, so that a project wanting to do its own LDAP queries can do so without duplicating code from the library for parsing settings like BIND_DN, BIND_PASSWORD, CONNECTION_OPTIONS. (Which in turn will inevitably leads to bugs). The benefit of having it in this library over just using python-ldap or ldap3 is being able to share the configuration logic for how to connect to the server.

we would really like to enforce the rule that "if an email address has an LDAP account, it can't [authenticate?]"
I’m not sure that rule would be shared by all users. Perhaps others would prefer their application first look up in the database, and then default to LDAP authentication, e.g. if only superusers are created in the database, the “regular” users living in the LDAP.

Agreed -- which is why the change here doesn't implement that sort of policy logic in the library; that belongs in the application.

Instead, LDAPReverseEmailSearch is designed to just provide a hook for applications with dual authentication to query LDAP data by email address, returning LDAPUser objects. Currently, an application with dual authentication that wants to enforce such a policy needs to fork or duplicate code from the library or call undocumented/internal methods. With this second commit, the application could implement that sort of enforcement on top of django-auth-ldap in a clean way.

I think of this as an optional hook for applications that want it, along the lines of https://django-auth-ldap.readthedocs.io/en/latest/permissions.html.

@francoisfreitag
Copy link
Member

I like the idea of extracting the connection for reuse. It is much clearer than tying it to the _LDAPUser. One of the difficulties it to keep the connection lazy, but certainly not a blocker.

I am not convinced by the chosen approach of extracting a mixin to factor out the logic to establish a connection. IMO, the connections should be an object on their own. LDAPConnection objects could infer the LDAP connection params on instantiation, based on the django settings. LDAPSettings already offers to filter django-auth-ldap settings.
A separate connection would also allow projects to create an LDAPConnection for their own purposes using the django-auth-ldap configuration.

A separate connection might also help solving #174.


Regarding the second part, casting LDAP results to users, I wonder about the use cases?
The problem presented in the PR description seems to be solvable by verifying if the LDAP returned results for the email search.
Why would one need to cast a bunch of LDAP search results into _LDAPUser objects, or even Django users? I find the direct population of attributes on the created _LDAPUser fairly hacky. It will also create Django users with the result of the search, which is unexpected from an LDAPReverseEmailSearch. I would expect the search to be reading data only?

@mateuszmandera
Copy link
Author

Opened #249 to address the LDAPConnection piece of the above comment first.

@mateuszmandera
Copy link
Author

Why would one need to cast a bunch of LDAP search results into _LDAPUser objects, or even Django users? I find the direct population of attributes on the created _LDAPUser fairly hacky. It will also create Django users with the result of the search, which is unexpected from an LDAPReverseEmailSearch. I would expect the search to be reading data only?

I agree that the should_populate feature might be unnecessary, I can just remove that. But returning _LDAPUsers as the result of search seems right to me - the django-auth-ldap thinks of users in terms of this abstraction, so returning _LDAPUsers when queried "find me the ldap users with this email" seems more appropriate than just raw search.execute() results. It's straightforward to change that if desirable though, so I don't feel very strongly about this point.

@francoisfreitag
Copy link
Member

Thinking about the original use case, the feature can probably be implemented by a LDAPBackend subclass:

  1. passing the email to check as an authenticate kwarg
  2. forwarding kwargs to authenticate_ldap_user
  3. Using a custom LDAPBackend subclass that uses the connection from the _LDAPUser to perform a search and verify the results count for a given email is 0.

That avoids establishing a separate LDAP connection and needing to rebind, and that remains related to the authentication of a user. What do you think?


But returning _LDAPUsers as the result of search seems right to me - the django-auth-ldap thinks of users in terms of this abstraction, so returning _LDAPUsers when queried "find me the ldap users with this email" seems more appropriate than just raw search.execute() results.

I’m unclear on the use case for this feature?
django-auth-ldap is not really a general LDAP query tool. It mostly aims to provide authentication for users against a LDAP, and populating the django user database from the LDAP.


In all cases, improvements can certainly be made towards facilitating the creation of an LDAPObject, so that projects can reuse the extraction and massaging of connection parameters from the settings (e.g. defining the server URI, with hooks so that e.g. the request can be used to find the server uri).
_LDAPUser could reuse that helper to instantiate its own connection.

@dwasyl
Copy link

dwasyl commented May 25, 2021

I’m unclear on the use case for this feature?
django-auth-ldap is not really a general LDAP query tool. It mostly aims to provide authentication for users against a LDAP, and populating the django user database from the LDAP.

Just for some extra context, in my use case's this would be handy so we could create a workflow to "pre-create" valid users and assign them permissions on the Django side in advance of their first login. We aren't able to get more groups created in LDAP and having users login once, then doing permissions and re-logging in isn't ideal.

@francoisfreitag
Copy link
Member

So you would basically crawl the LDAP based on some search pattern, and create the corresponding Django users?

@dwasyl
Copy link

dwasyl commented May 25, 2021 via email

@francoisfreitag
Copy link
Member

To crawl the LDAP, the authentication backend and authentication machinery is not needed.
It would be great to pull the connection out of the _LDAPUser so that applications can make connections to the LDAP and crawl it to their liking. IMO, that means a LDAPConnection should be created, that is able to discover the settings from the Django settings.
In turn, that means the logic to extract the auth ldap settings currently stored on the LDAPBackend should be factored out, so that other classes can get the django-auth-ldap settings.


To instantiate a user, the code on this PR makes a new search per user being constructed. Since the username comes from the LDAP, these searches are unnecessary and should be avoided.

mateuszmandera added a commit to mateuszmandera/zulip that referenced this pull request Aug 26, 2021
Till now, we've been forking django-auth-ldap at
https://github.com/zulip/django-auth-ldap to put the
LDAPReverseEmailSearch feature in it, hoping to get it merged
upstream in django-auth-ldap/django-auth-ldap#150

The efforts to get it merged have stalled for now however and we don't
want to be on the fork forever, so this commit puts the email search
feature as a clumsy workaround inside our codebase and switches to using
the latest upstream release instead of the fork.
mateuszmandera added a commit to mateuszmandera/zulip that referenced this pull request Aug 26, 2021
Till now, we've been forking django-auth-ldap at
https://github.com/zulip/django-auth-ldap to put the
LDAPReverseEmailSearch feature in it, hoping to get it merged
upstream in django-auth-ldap/django-auth-ldap#150

The efforts to get it merged have stalled for now however and we don't
want to be on the fork forever, so this commit puts the email search
feature as a clumsy workaround inside our codebase and switches to using
the latest upstream release instead of the fork.
mateuszmandera added a commit to mateuszmandera/zulip that referenced this pull request Aug 26, 2021
Till now, we've been forking django-auth-ldap at
https://github.com/zulip/django-auth-ldap to put the
LDAPReverseEmailSearch feature in it, hoping to get it merged
upstream in django-auth-ldap/django-auth-ldap#150

The efforts to get it merged have stalled for now however and we don't
want to be on the fork forever, so this commit puts the email search
feature as a clumsy workaround inside our codebase and switches to using
the latest upstream release instead of the fork.
timabbott pushed a commit to mateuszmandera/zulip that referenced this pull request Aug 29, 2021
Till now, we've been forking django-auth-ldap at
https://github.com/zulip/django-auth-ldap to put the
LDAPReverseEmailSearch feature in it, hoping to get it merged
upstream in django-auth-ldap/django-auth-ldap#150

The efforts to get it merged have stalled for now however and we don't
want to be on the fork forever, so this commit puts the email search
feature as a clumsy workaround inside our codebase and switches to using
the latest upstream release instead of the fork.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants