Projet

Général

Profil

0001-ldap-add-method-to-get-ppolicy-operational-attribute.patch

Loïc Dachary, 04 mai 2021 11:29

Télécharger (8,94 ko)

Voir les différences:

Subject: [PATCH] ldap: add method to get ppolicy operational attributes
 (#51239)

Fixes: #51239

License: MIT
 src/authentic2/backends/ldap_backend.py | 74 +++++++++++++++++++++----
 tests/test_ldap.py                      | 49 +++++++++++++++-
 2 files changed, 111 insertions(+), 12 deletions(-)
src/authentic2/backends/ldap_backend.py
299 299
    raise NotImplementedError
300 300

  
301 301

  
302
def password_policy_control_messages(ctrl):
302
def password_policy_control_messages(ctrl, attributes):
303 303
    messages = []
304 304

  
305 305
    if ctrl.error:
306 306
        error = ppolicy.PasswordPolicyError.namedValues[ctrl.error]
307 307
        error2message = {
308
            'passwordExpired': _('The password expired'),
309
            'accountLocked': _('The account is locked.'),
308
            'passwordExpired': _('The password expired after {pwdmaxage}').format(**attributes),
309
            'accountLocked': _(
310
                'The account is locked since {pwdaccountlockedtime[0]} after {pwdmaxfailure} failures.'
311
            ).format(**attributes),
310 312
            'changeAfterReset': _('The password was reset and must be changed.'),
311 313
            'passwordModNotAllowed': _('It is not possible to modify the password.'),
312 314
            'mustSupplyOldPassword': _('The old password must be supplied.'),
313 315
            'insufficientPasswordQuality': _('The password does not meet the quality requirements.'),
314
            'passwordTooShort': _('The password is too short.'),
315
            'passwordTooYoung': _('It is too soon to change the password.'),
316
            'passwordInHistory': _('This password was recently used and cannot be used again.'),
316
            'passwordTooShort': _('The password is too short {pwdminlength}.').format(**attributes),
317
            'passwordTooYoung': _('It is too soon to change the password {pwdminage}.').format(**attributes),
318
            'passwordInHistory': _(
319
                'This password is among the last {pwdhistory} password that were used and cannot be used again.'
320
            ).format(**attributes),
317 321
        }
318 322
        messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error)))
319 323
        return messages
......
621 625
        'user_attributes': [],
622 626
        # https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap-controls
623 627
        'use_controls': True,
628
        'ppolicy_dn': '',
624 629
    }
625 630
    _REQUIRED = ('url', 'basedn')
626 631
    _TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive')
......
659 664
        log.debug('got config %r', blocks)
660 665
        return blocks
661 666

  
662
    @staticmethod
663
    def process_controls(request, authz_id, ctrls):
667
    @classmethod
668
    def process_controls(cls, request, block, conn, authz_id, ctrls):
669
        attributes = cls.get_ppolicy_attributes(block, conn, authz_id)
664 670
        for c in ctrls:
665 671
            if c.controlType == ppolicy.PasswordPolicyControl.controlType:
666
                message = ' '.join(password_policy_control_messages(c))
672
                message = ' '.join(password_policy_control_messages(c, attributes))
667 673
                if request is not None:
668 674
                    messages.add_message(request, messages.WARNING, message)
669 675
                    if c.graceAuthNsRemaining or c.timeBeforeExpiration:
......
791 797
                            else:
792 798
                                serverctrls = []
793 799
                            results = conn.simple_bind_s(authz_id, password, serverctrls=serverctrls)
794
                            self.process_controls(request, authz_id, results[3])
800
                            self.process_controls(request, block, conn, authz_id, results[3])
795 801
                            user_login_success(authz_id)
796 802
                            if not block['connect_with_user_credentials']:
797 803
                                try:
......
803 809
                        except ldap.INVALID_CREDENTIALS as e:
804 810
                            if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]:
805 811
                                self.process_controls(
806
                                    request, authz_id, DecodeControlTuples(e.args[0]['ctrls'])
812
                                    request, block, conn, authz_id, DecodeControlTuples(e.args[0]['ctrls'])
807 813
                                )
808 814
                            attributes = self.get_ldap_attributes(block, conn, authz_id)
809 815
                            user = self.lookup_existing_user(authz_id, block, attributes)
......
1167 1173
                    attributes.add(at_mapping[key])
1168 1174
        return list(set(attribute.lower() for attribute in attributes))
1169 1175

  
1176
    @classmethod
1177
    def get_ppolicy_attributes(cls, block, conn, dn):
1178
        def get_attributes(dn, attributes):
1179
            try:
1180
                results = conn.search_s(dn, ldap.SCOPE_BASE, u'(objectclass=*)', attributes)
1181
            except ldap.LDAPError as e:
1182
                log.error('unable to retrieve attributes of dn %r: %r', dn, e)
1183
                return {}
1184
            results = cls.normalize_ldap_results(results)
1185
            attributes_results.update(results[0][1])
1186
            return attributes_results
1187

  
1188
        user_attributes = [
1189
            'pwdaccountlockedtime',
1190
            'pwdchangedtime',
1191
            'pwdfailuretime',
1192
            'pwdgraceusetime',
1193
            'pwdhistory',
1194
            'pwdreset',
1195
        ]
1196
        ppolicy_attributes = [
1197
            'pwdminage',
1198
            'pwdmaxage',
1199
            'pwdinhistory',
1200
            'pwdcheckquality',
1201
            'pwdminlength',
1202
            'pwdexpirewarning',
1203
            'pwdgraceauthnlimit',
1204
            'pwdlockout',
1205
            'pwdlockoutduration',
1206
            'pwdmaxfailure',
1207
            'pwdmaxrecordedfailure',
1208
            'pwdfailurecountinterval',
1209
            'pwdmustchange',
1210
            'pwdallowuserchange',
1211
            'pwdsafemodify',
1212
        ]
1213
        attributes_results = {k: [] for k in user_attributes + ppolicy_attributes}
1214

  
1215
        attributes_results.update(get_attributes(dn, user_attributes))
1216
        ppolicy_dn = block.get('ppolicy_dn')
1217
        if ppolicy_dn:
1218
            attributes_results.update(**get_attributes(ppolicy_dn, ppolicy_attributes))
1219

  
1220
        return attributes_results
1221

  
1170 1222
    @classmethod
1171 1223
    def get_ldap_attributes(cls, block, conn, dn):
1172 1224
        """Retrieve some attributes from LDAP, add mandatory values then apply
tests/test_ldap.py
1156 1156
    ppolicy_authenticate_exactly_pwdMaxFailure(slapd_ppolicy, caplog)
1157 1157
    assert 'account is locked' not in caplog.text
1158 1158
    assert authenticate(username=USERNAME, password='incorrect') is None
1159
    assert 'account is locked' in caplog.text
1159
    assert 'account is locked since 20' in caplog.text
1160 1160

  
1161 1161

  
1162 1162
def test_do_not_use_controls(slapd_ppolicy, settings, db, caplog):
......
1180 1180
    assert 'account is locked' not in caplog.text
1181 1181

  
1182 1182

  
1183
def test_get_ppolicy_attributes(slapd_ppolicy, settings, db):
1184
    settings.LDAP_AUTH_SETTINGS = [{
1185
        'url': [slapd_ppolicy.ldap_url],
1186
        'basedn': u'o=ôrga',
1187
        'ppolicy_dn': u'cn=default,ou=ppolicies,o=ôrga',
1188
        'use_tls': False,
1189
    }]
1190

  
1191
    pwdMaxAge = 1
1192
    slapd_ppolicy.add_ldif('''
1193
dn: cn=default,ou=ppolicies,o=ôrga
1194
cn: default
1195
objectclass: top
1196
objectclass: device
1197
objectclass: pwdPolicy
1198
objectclass: pwdPolicyChecker
1199
pwdAttribute: userPassword
1200
pwdMinAge: 0
1201
pwdMaxAge: {pwdMaxAge}
1202
pwdInHistory: 1
1203
pwdCheckQuality: 0
1204
pwdMinLength: 0
1205
pwdExpireWarning: 0
1206
pwdGraceAuthnLimit: 0
1207
pwdLockout: TRUE
1208
pwdLockoutDuration: 0
1209
pwdMaxFailure: 0
1210
pwdMaxRecordedFailure: 0
1211
pwdFailureCountInterval: 0
1212
pwdMustChange: FALSE
1213
pwdAllowUserChange: TRUE
1214
pwdSafeModify: FALSE
1215
'''.format(pwdMaxAge=pwdMaxAge))
1216

  
1217
    user = authenticate(username=USERNAME, password=UPASS)
1218
    assert user.check_password(UPASS)
1219
    password = u'ogutOmyetew4'
1220
    user.set_password(password)
1221

  
1222
    time.sleep(pwdMaxAge * 3)
1223

  
1224
    conn = ldap_backend.LDAPBackend.get_connection(settings.LDAP_AUTH_SETTINGS[0])
1225
    attributes = ldap_backend.LDAPBackend.get_ppolicy_attributes(settings.LDAP_AUTH_SETTINGS[0], conn, DN)
1226
    assert 'pwdchangedtime' in attributes
1227
    assert attributes['pwdmaxage'] == [str(pwdMaxAge)]
1228

  
1229

  
1183 1230
def test_authenticate_ppolicy_pwdGraceAuthnLimit(slapd_ppolicy, settings, db, caplog):
1184 1231
    settings.LDAP_AUTH_SETTINGS = [
1185 1232
        {
1186
-