Skip to content

Commit

Permalink
ldap: Fix searching for binary (bytes) attributes
Browse files Browse the repository at this point in the history
This is needed e.g. when getting someone's group list which requires
filtering by the binary objectSid attribute.
  • Loading branch information
ThiefMaster committed Feb 25, 2020
1 parent a1b1c66 commit f26be36
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 5 deletions.
2 changes: 1 addition & 1 deletion flask_multipass/__init__.py
Expand Up @@ -12,7 +12,7 @@
from .group import Group
from .identity import IdentityProvider

__version__ = '0.3-dev3'
__version__ = '0.3-dev4'
__all__ = ('Multipass', 'AuthProvider', 'IdentityProvider', 'AuthInfo', 'IdentityInfo', 'Group', 'MultipassException',
'AuthenticationFailed', 'IdentityRetrievalFailed', 'GroupRetrievalFailed', 'NoSuchUser',
'InvalidCredentials')
24 changes: 20 additions & 4 deletions flask_multipass/providers/ldap/util.py
Expand Up @@ -11,18 +11,19 @@
from warnings import warn

import ldap
from flask import appcontext_tearing_down, g, has_app_context, current_app
from flask import appcontext_tearing_down, current_app, g, has_app_context
from ldap.controls import SimplePagedResultsControl
from ldap.filter import filter_format
from ldap.filter import escape_filter_chars
from ldap.ldapobject import ReconnectLDAPObject
from werkzeug.urls import url_parse

from flask_multipass._compat import iteritems, itervalues
from flask_multipass._compat import PY2, iteritems, itervalues, text_type
from flask_multipass.exceptions import MultipassException
from flask_multipass.providers.ldap.exceptions import LDAPServerError
from flask_multipass.providers.ldap.globals import _ldap_ctx_stack, current_ldap
from flask_multipass.util import convert_app_data


#: A context holding the LDAP connection and the LDAP provider settings.
LDAPContext = namedtuple('LDAPContext', ('connection', 'settings'))

Expand Down Expand Up @@ -186,6 +187,21 @@ def _build_assert_template(value, exact):
return '(|{})'.format(assert_template * len(value))


def _escape_filter_chars(value):
if isinstance(value, text_type):
return escape_filter_chars(value)
elif PY2:
return ''.join('\\%02x' % ord(c) for c in value)
else:
return ''.join('\\%02x' % c for c in value)


def _filter_format(filter_template, assertion_values):
# like python-ldap's filter_format, but handles binary data (bytes) gracefully by escaping
# everything so things don't break when searching e.g. for someone's binary objectSid
return filter_template % tuple(_escape_filter_chars(v) for v in assertion_values)


def build_search_filter(criteria, type_filter, mapping=None, exact=False):
"""Builds a valid LDAP search filter for retrieving entries.
Expand All @@ -204,7 +220,7 @@ def build_search_filter(criteria, type_filter, mapping=None, exact=False):
if not assertions:
return None
filter_template = '(&{}{})'.format("".join(assert_templates), type_filter)
return filter_format(filter_template, (item for assertion in assertions for item in assertion))
return _filter_format(filter_template, (item for assertion in assertions for item in assertion))


def get_page_cookie(server_ctrls):
Expand Down
1 change: 1 addition & 0 deletions tests/providers/ldap/test_util.py
Expand Up @@ -26,6 +26,7 @@
@pytest.mark.parametrize(('criteria', 'type_filter', 'mapping', 'exact', 'expected'), (
({}, '', None, True, None),
({}, '', '(|(objectClass=Person)(objectCategory=user))', True, None),
({'cn': ['foobar'], 'objectSid': [b'\00\xa0foo']}, '', None, True, r"(&(cn=foobar)(objectSid=\00\a0\66\6f\6f))"),
({'givenName': ['Alain'], 'sn': ["D'Issoir"]}, '', None, True, "(&(givenName=Alain)(sn=D'Issoir))"),
({'givenName': ['Alain'], 'last_name': ["D'Issoir"]}, '', {'last_name': 'sn'}, True,
"(&(givenName=Alain)(sn=D'Issoir))"),
Expand Down

0 comments on commit f26be36

Please sign in to comment.