Projet

Général

Profil

0001-ldap-do-not-block-check_password-and-get_attributes-.patch

Benjamin Dauvergne, 26 juin 2019 11:15

Télécharger (6,06 ko)

Voir les différences:

Subject: [PATCH] ldap: do not block check_password and get_attributes if LDAP
 is down (#34316)

 src/authentic2/attributes_ng/sources/ldap.py |  2 +-
 src/authentic2/backends/ldap_backend.py      | 36 ++++++++++++-----
 tests/test_ldap.py                           | 42 ++++++++++++++++++++
 3 files changed, 68 insertions(+), 12 deletions(-)
src/authentic2/attributes_ng/sources/ldap.py
38 38
def get_attributes(instance, ctx):
39 39
    user = ctx.get('user')
40 40
    if user and isinstance(user, LDAPUser):
41
        ctx.update(user.get_attributes())
41
        ctx.update(user.get_attributes(instance, ctx))
42 42
    return ctx
src/authentic2/backends/ldap_backend.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import hashlib
18

  
17 19
try:
18 20
    import ldap
19 21
    import ldap.modlist
......
35 37
# code originaly copied from by now merely inspired by
36 38
# http://www.amherst.k12.oh.us/django-ldap.html
37 39

  
40
from django.core.cache import cache
38 41
from django.core.exceptions import ImproperlyConfigured
39 42
from django.conf import settings
40 43
from django.contrib.auth import get_user_model
......
70 73
    '/var/lib/ca-certificates/ca-bundle.pem',  # OpenSuse
71 74
]
72 75

  
76

  
73 77
# Select a system certificate store
74 78
for bundle_path in CA_BUNDLE_PATHS:
75 79
    if os.path.exists(bundle_path):
......
327 331

  
328 332
    def check_password(self, raw_password):
329 333
        connection = self.ldap_backend.get_connection(self.block)
330
        try:
331
            connection.simple_bind_s(self.dn, raw_password)
332
        except ldap.INVALID_CREDENTIALS:
333
            return False
334
        except ldap.LDAPError as e:
335
            log.error('LDAPUser.check_password() failed: %r', e)
336
            return False
337
        self._current_password = raw_password
338
        return True
334
        if connection:
335
            try:
336
                connection.simple_bind_s(self.dn, raw_password)
337
                self._current_password = raw_password
338
                return True
339
            except ldap.INVALID_CREDENTIALS:
340
                return False
341
            except ldap.LDAPError as e:
342
                log.warning('LDAPUser.check_password() failed: %r', e)
343
        if getattr(self, '_current_password', None) is not None:
344
            return raw_password == self._current_password
345
        return False
339 346

  
340 347
    def set_password(self, new_password):
341 348
        # Allow change password to work in all cases, as the form does a check_password() first
......
365 372
        self.ldap_backend.update_default(self.block, validate=False)
366 373
        return self.ldap_backend.get_connection(self.block, credentials=credentials)
367 374

  
368
    def get_attributes(self):
375
    def get_attributes(self, attribute_source, ctx):
369 376
        conn = self.get_connection()
370
        return self.ldap_backend.get_ldap_attributes(self.block, conn, self.dn) or {}
377
        key = hashlib.md5((force_text(str(self.pk)) + ';' + force_text(self.dn)).encode('utf-8')).hexdigest()
378
        # prevents blocking on temporary LDAP failures
379
        if conn is not None:
380
            attributes = self.ldap_backend.get_ldap_attributes(self.block, conn, self.dn) or {}
381
            # keep attributes in cache for 8 hours
382
            cache.set(key, attributes, 3600 * 8)
383
            return attributes
384
        return cache.get(key, {})
371 385

  
372 386
    def save(self, *args, **kwargs):
373 387
        if hasattr(self, 'keep_pk'):
tests/test_ldap.py
48 48
CN = 'Étienne Michu'
49 49
DN = 'cn=%s,o=ôrga' % escape_dn_chars(CN)
50 50
PASS = 'passé'
51
UPASS = u'passé'
51 52
EMAIL = 'etienne.michu@example.net'
52 53

  
53 54
base_dir = os.path.dirname(__file__)
......
836 837
        response = client.post('/login/', {'login-password-submit': '1',
837 838
                                           'username': USERNAME,
838 839
                                           'password': PASS}, follow=True)
840

  
841

  
842
def test_get_attributes(slapd, settings, db, rf):
843
    settings.LDAP_AUTH_SETTINGS = [{
844
        'url': [slapd.ldap_url],
845
        'basedn': u'o=ôrga',
846
        'use_tls': False,
847
    }]
848
    user = authenticate(username=USERNAME, password=UPASS)
849
    assert user
850
    assert user.get_attributes(object(), {}) == {
851
        'dn': u'cn=\xc9tienne Michu,o=\xf4rga',
852
        'givenname': [u'\xc9tienne'],
853
        'mail': [u'etienne.michu@example.net'],
854
        'sn': [u'Michu'],
855
        'uid': [u'etienne.michu'],
856
    }
857
    # simulate LDAP down
858
    slapd.stop()
859
    assert user.get_attributes(object(), {}) == {
860
        'dn': u'cn=\xc9tienne Michu,o=\xf4rga',
861
        'givenname': [u'\xc9tienne'],
862
        'mail': [u'etienne.michu@example.net'],
863
        'sn': [u'Michu'],
864
        'uid': [u'etienne.michu'],
865
    }
866
    assert not user.check_password(UPASS)
867
    # simulate LDAP come back up
868
    slapd.start()
869
    assert user.check_password(UPASS)
870
    # modify LDAP record and check attributes are updated
871
    conn = slapd.get_connection_admin()
872
    ldif = [(ldap.MOD_REPLACE, 'sn', ['Micho'])]
873
    conn.modify_s(DN, ldif)
874
    assert user.get_attributes(object(), {}) == {
875
        'dn': u'cn=\xc9tienne Michu,o=\xf4rga',
876
        'givenname': [u'\xc9tienne'],
877
        'mail': [u'etienne.michu@example.net'],
878
        'sn': [u'Micho'],
879
        'uid': [u'etienne.michu'],
880
    }
839
-