Coder Social home page Coder Social logo

pyramid_ldap's Introduction

Pyramid LDAP

pyramid_ldap provides LDAP authentication services for your Pyramid application. Thanks to the ever-awesome SurveyMonkey for sponsoring the development of this package!

See the documentation at https://docs.pylonsproject.org/projects/pyramid_ldap/en/latest/ for more information.

This package will only work with Pyramid 1.3 and later.

Installation

pyramid_ldap uses pyldap which in turn requires libldap2 and libsasl2 development headers installed.

On Ubuntu 16.04 you can install them using the command apt-get install libldap2-dev libsasl2-dev.

pyramid_ldap's People

Contributors

digitalresistor avatar ericrasmussen avatar goodwillcoding avatar id64f avatar mcdonc avatar msander avatar pipoun avatar pjenvey avatar stevepiercy avatar timgates42 avatar travisfischer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyramid_ldap's Issues

Feature: Settings-based configuration for LDAP

The majority of my configuration is handled from a settings file, and I'd like my usage and configuration of pyramid_ldap to use this as well. I'm happy to do the development work, but want to check the feature will be suitable for inclusion in this package.

My thinking is that some additional work can be done in the includeme() function, after the existing directives are added, to read the relevant configuration in from the settings, and then have that call these existing LDAP configuration functions. Backwards compatibility is maintained, and then all LDAP configuration can be carried out in a settings file.

Thoughts?

Authorization fails if the user is a member of a group through another group

The groupfinder() routine looks for groups which have the user's dn as a member, but if the authorization is performed by adding an entire group to the target group - members of that group are not authorized.

This can be solved by doing a recursive lookup returning all group dn-s for a user, including those who satisfy the group query for each group.

Cyclic group membership could happen in real life, and should be detected and avioded.

would like to be able to use a template for base_dn

I would like to search on different parts of the LDAP tree depending on various requests. It would be nice if we can pass a base dn/suffix to the authenticate() methods...

Something like:
ou=People, o=%(suffix)s, dc=test,dc=com

Is 'bind' and 'passwd' mandatory?

According to the documentation, ldap_setup doesn't seem to need values for bindand passwd. But if you leave them None (as they are by default) you will end up with an AttributeError within ldappool.

2015-06-12 14:52:10,165 ERROR [phoenix.views][MainThread] unknown failure
Traceback (most recent call last):
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/tweens.py", line 21, in excview_tween
    response = handler(request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/router.py", line 163, in handle_request
    response = view_callable(context, request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/config/views.py", line 329, in attr_view
    return view(context, request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/config/views.py", line 305, in predicate_wrapper
    return view(context, request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/config/views.py", line 245, in _secured_view
    return view(context, request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/config/views.py", line 385, in viewresult_to_response
    result = view(context, request)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid-1.5.7-py2.7.egg/pyramid/config/views.py", line 477, in _class_requestonly_view
    response = getattr(inst, attr)()
  File "/home/fklemme/Repositories/pyramid-phoenix/phoenix/views/account.py", line 177, in ldap
    auth = connector.authenticate(username, password)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/pyramid_ldap-0.1-py2.7.egg/pyramid_ldap/__init__.py", line 111, in authenticate
    with self.manager.connection() as conn:
  File "/home/fklemme/.conda/envs/birdhouse/lib/python2.7/contextlib.py", line 17, in __enter__
    return self.gen.next()
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/ldappool-1.0-py2.7.egg/ldappool/__init__.py", line 291, in connection
    conn = self._get_connection(bind, passwd)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/ldappool-1.0-py2.7.egg/ldappool/__init__.py", line 235, in _get_connection
    conn = self._match(bind, passwd)
  File "/home/fklemme/Repositories/pyramid-phoenix/eggs/ldappool-1.0-py2.7.egg/ldappool/__init__.py", line 142, in _match
    passwd = passwd.encode('utf8')
AttributeError: 'NoneType' object has no attribute 'encode'

If bindand passwd are mandatory, should we remove these misleading default values?

Active directory login accepted with empty string as password using sample application

While trying to implement active directory auth with pyramid_ldap for a pyramid project, I came across an issue which I can replicate using code from the sample app.

When adapting the code to authenticate using our AD domain controller, any existing username can successfully log in using the empty string as password.

To replicate:

  • Create a virtual environment and a new pyramid project:
    virtualenv2 --no-site-packages pyramid_ldap_test_env
    cd pyramid_ldap_test_env
    bin/easy_install pyramid
    bin/pcreate -s alchemy ldap_test
    cd ldap_test
    ../bin/python setup.py develop
    ../bin/easy_install pyramid_ldap
  • Include pyramid_ldap in pyramid_includes in development.ini
  • Edit the projects "init,py":
    import ldap
    from pyramid.config import Configurator
    from pyramid.security import (Allow, Authenticated, DENY_ALL)
    from pyramid.authentication import AuthTktAuthenticationPolicy
    from pyramid.authorization import ACLAuthorizationPolicy
    from pyramid_ldap import groupfinder

    class RootFactory(object):
        __acl__ = [(Allow, Authenticated, 'view'), DENY_ALL]

        def __init__(self, request):
            pass

    def main(global_config, **settings):
        """ This function returns a Pyramid WSGI application.
        """
        engine = engine_from_config(settings, 'sqlalchemy.')
        DBSession.configure(bind=engine)
        Base.metadata.bind = engine
        config = Configurator(settings=settings)

        # setup auth
        config = Configurator(settings=settings, root_factory=RootFactory)
        config.set_authentication_policy(
            AuthTktAuthenticationPolicy('seekr1t', callback=groupfinder)
            )
        config.set_authorization_policy(ACLAuthorizationPolicy())

        # ldap setup
        config.ldap_setup('ldap://dc.ge.lan', bind='CN=Firstname Lastname,CN=Users,DC=ge,DC=lan', passwd='thepassword')
        config.ldap_set_login_query(base_dn='CN=Users,DC=ge,DC=lan',
            filter_tmpl='(sAMAccountName=%(login)s)', scope=ldap.SCOPE_ONELEVEL)
        config.ldap_set_groups_query(base_dn='CN=Users,DC=ge,DC=lan',
            filter_tmpl='(&(objectCategory=group)(member=%(userdn)s))',
            scope=ldap.SCOPE_SUBTREE, cache_period=600)

        config.add_route('root', '/')
        config.add_route('login', '/login')
        config.add_route('logout', '/logout')
        config.scan()
        return config.make_wsgi_app()
  • views.py:
    from pyramid.response import Response
    from pyramid.view import view_config, forbidden_view_config
    from pyramid.httpexceptions import HTTPFound
    from pyramid.security import (remember, forget)

    from pyramid_ldap import (get_ldap_connector, groupfinder)

    @view_config(route_name='login',
                 renderer='templates/login.pt')
    @forbidden_view_config(renderer='templates/login.pt')
    def login(request):
        url = request.current_route_url()
        login = ''
        password = ''
        error = ''

        if 'form.submitted' in request.POST:
            login = request.POST['login']
            password = request.POST['password']
            connector = get_ldap_connector(request)
            data = connector.authenticate(login, password)
            if data is not None:
                dn = data[0]
                headers = remember(request, dn)
                return HTTPFound('/', headers=headers)
            else:
                error = 'Invalid credentials'

        return dict(login_url=url, login=login, password=password, error=error)

    @view_config(route_name='root', permission='view')
    def logged_in(request):
        return Response('OK')

    @view_config(route_name='logout')
    def logout(request):
        headers = forget(request)
        return Response('Logged out', headers=headers)
  • Login.pt
<html>
    <head>
    <title>Login</title>
    </head>
    <body>
    <h1>Log In</h1>
      <p tal:replace="error"/>
      <form action="${login_url}" method="POST">
        Login: <input type="text" name="login" value="${login}"/>
        <br/>
        Password: <input type="password" name="password" value="${password}"/>
        <br/>
        <input type="hidden" name="form.submitted"/>
        <input type="submit" name="submit" value="Log In"/>
      </form>
    </body>
    </html>
  • Start the application using ..bin/pserve development.ini.
  • Try logging in as active directory user:
    • login with the wrong password doesn't work.
    • login with the correct password works.
    • login with the empty string as password works.

Can anyone confirm this or am I missing something?

LDAP referrals are bound using "<ROOT>" as the binding DN

If a query returns ldap referrals, pyramid_ldap binds to the referrals using "" as the binding DN (with no password). When it tries querying against these referrals, an error is returned ("proper bind must be made...").

This could be avoided if there was some way to change the protocol version used (ldap referrals are "new" in version 3). Or subsequent connections could use the binding dn used in the first connection

pyramid_ldap depends both on python-ldap and pyldap

If I install pyramid_ldap using pip (in Python 2.7), it tries to install python-ldap (required by pyramid_ldap) as well as it's fork pyldap (required by ldappool==2.0).

According to pyldap documentation, one can not/should not install both libraries alongside.
Both install under "ldap".

I fixed this locally by adding this line to my applications requirements.txt to get rid of the pyldap dependency:
ldappool==1.0

python-ldap is easier to install on windows then pyldap, as it lists several installers and a wheel file in the package index.
So maybe it makes sense to fix the ldappool version inside pyramid_ldap?

no mechanism to filter group lookups

connector.user_groups(user_dn) returns a full list of group attributes.

Typically, this list of attributes includes group members -- a potentially substantial amount of data to retrieve and cache, and not typically useful when simply trying to retrieve a user's group membership information for authorization purposes (in which case it is typical that only dn, and possibly cn, may be needed).

Using pyramid_ldap with OpenLDAP and PosixGroups

Has anyone ever tried to use "PosixGroups" that do not contain the full userdn but only an attribute (uid) of the users of that group? I have not yet managed to write the filters to work with such an environment, as the only available placeholders are login and userdn.

Any ideas how to achieve this? It looks like a further LDAP query is required and it will not work without changing some code.

Incompatible with Pyramid 2.0

The module pyramid.compat has been removed in 2.0. pyramid_ldap has to be changed accordingly.

  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/config/__init__.py", line 632, in include
    c = self.maybe_dotted(callable)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/config/__init__.py", line 740, in maybe_dotted
    return self.name_resolver.maybe_resolve(dotted)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/path.py", line 327, in maybe_resolve
    return self._resolve(dotted, package)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/path.py", line 334, in _resolve
    return self._zope_dottedname_style(dotted, package)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/path.py", line 383, in _zope_dottedname_style
    found = __import__(used)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid_ldap/__init__.py", line 19, in <module>
    from pyramid.compat import (
ModuleNotFoundError: No module named 'pyramid.compat'

Also, set_request_property is no longer available:

  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/config/actions.py", line 573, in wrapper
    result = wrapped(self, *arg, **kw)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid_ldap/__init__.py", line 304, in ldap_setup
    config.set_request_property(get_connector, 'ldap_connector', reify=True)
  File "/home/marcel/.pyenv/versions/3.9.2/envs/dash/lib/python3.9/site-packages/pyramid/config/__init__.py", line 716, in __getattr__
    raise AttributeError(name)
AttributeError: set_request_property

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.