Projet

Général

Profil

0001-auth_saml-implement-attribute-provisionning-after-fi.patch

Benjamin Dauvergne, 07 août 2019 14:29

Télécharger (6,7 ko)

Voir les différences:

Subject: [PATCH] auth_saml: implement attribute provisionning after first
 login (#35283)

Also fix bug in finish_create_user() where modified user was not saved.
 src/authentic2_auth_saml/adapters.py | 53 ++++++++++++++++++++--------
 tests/test_auth_saml.py              | 20 +++++++----
 2 files changed, 52 insertions(+), 21 deletions(-)
src/authentic2_auth_saml/adapters.py
16 16

  
17 17
import logging
18 18

  
19
from mellon.adapters import DefaultAdapter
19
from mellon.adapters import DefaultAdapter, UserCreationError
20 20
from mellon.utils import get_setting
21 21

  
22 22
from authentic2 import utils
23 23

  
24
logger = logging.getLogger('authentic2.auth_saml')
25

  
24 26

  
25 27
class AuthenticAdapter(DefaultAdapter):
26 28
    def create_user(self, user_class):
27 29
        return user_class.objects.create()
28 30

  
29 31
    def finish_create_user(self, idp, saml_attributes, user):
32
        self.provision_a2_attributes(user, idp, saml_attributes, do_raise=True)
33

  
34
    def provision(self, user, idp, saml_attributes):
35
        super(AuthenticAdapter, self).provision(user, idp, saml_attributes)
36
        self.provision_a2_attributes(user, idp, saml_attributes)
37

  
38
    def provision_a2_attributes(self, user, idp, saml_attributes, do_raise=False):
30 39
        '''Copy incoming SAML attributes to user attributes, A2_ATTRIBUTE_MAPPING must be a list of
31 40
           dictinnaries like:
32 41

  
......
40 49
            If an attribute is not mandatory any error is just logged, if the attribute is
41 50
            mandatory, login will fail.
42 51
        '''
43
        log = logging.getLogger(__name__)
44 52

  
45 53
        attribute_mapping = get_setting(idp, 'A2_ATTRIBUTE_MAPPING', [])
54
        user_modified = False
46 55
        for mapping in attribute_mapping:
47 56
            attribute = mapping['attribute']
48 57
            saml_attribute = mapping['saml_attribute']
49 58
            mandatory = mapping.get('mandatory', False)
50
            if not saml_attributes.get(saml_attribute):
59
            logger.debug('auth_saml: trying mapping attribute from %r to %r', saml_attribute, attribute,
60
                         extra={'user': user})
61
            if saml_attribute not in saml_attributes:
51 62
                if mandatory:
52
                    log.error('mandatory saml attribute %r is missing', saml_attribute,
53
                              extra={'attributes': repr(saml_attributes)})
54
                    raise ValueError('missing attribute')
63
                    logger.error('auth_saml: mandatory saml attribute %r is missing', saml_attribute,
64
                                 extra={'attributes': repr(saml_attributes), 'user': user})
65
                    if do_raise:
66
                        raise UserCreationError('missing saml_attribute %r' % saml_attribute)
55 67
                else:
56
                    continue
68
                    logger.debug('auth_saml: saml_attribute %r not found', saml_attribute, extra={'user': user})
69
                continue
57 70
            try:
58 71
                value = saml_attributes[saml_attribute]
59
                self.set_user_attribute(user, attribute, value)
72
                if self.set_user_attribute(user, attribute, value):
73
                    user_modified = True
60 74
            except Exception as e:
61
                log.error(u'failed to set attribute %r from saml attribute %r with value %r: %s',
62
                          attribute, saml_attribute, value, e,
63
                          extra={'attributes': repr(saml_attributes)})
64
                if mandatory:
65
                    raise
75
                logger.error(u'failed to set attribute %r from saml attribute %r with value %r: %s',
76
                             attribute, saml_attribute, value, e,
77
                             extra={'attributes': repr(saml_attributes), 'user': user})
78
                if mandatory and do_raise:
79
                    raise UserCreationError('could not set attribute %s' % attribute, e)
80
        if user_modified:
81
            user.save()
66 82

  
67 83
    def set_user_attribute(self, user, attribute, value):
68 84
        if isinstance(value, list):
......
70 86
                raise ValueError('too much values')
71 87
            value = value[0]
72 88
        if attribute in ('first_name', 'last_name', 'email', 'username'):
73
            setattr(user, attribute, value)
89
            if getattr(user, attribute) != value:
90
                logger.info('auth_saml: attribute %r set to %r', attribute, value, extra={'user': user})
91
                setattr(user, attribute, value)
92
                return True
74 93
        else:
75
            setattr(user.attributes, attribute, value)
94
            if getattr(user.attributes, attribute) != value:
95
                logger.info('auth_saml: attribute %r set to %r', attribute, value, extra={'user': user})
96
                setattr(user.attributes, attribute, value)
97
                return True
98
        return False
76 99

  
77 100
    def auth_login(self, request, user):
78 101
        utils.login(request, user, 'saml')
tests/test_auth_saml.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 logging
18

  
17 19
import pytest
18 20

  
21
import lasso
22

  
19 23
from django.contrib.auth import get_user_model
20 24
from authentic2.models import Attribute
21 25

  
22
pytestmark = pytest.mark.django_db
23 26

  
24

  
25
def test_provision_attributes():
27
def test_provision_attributes(db, caplog):
26 28
    from authentic2_auth_saml.adapters import AuthenticAdapter
27 29

  
28 30
    adapter = AuthenticAdapter()
......
45 47
    }
46 48

  
47 49
    saml_attributes = {
50
        u'issuer': 'https://idp.com/',
51
        u'name_id_content': 'xxx',
52
        u'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT,
48 53
        u'mail': u'john.doe@example.com',
49 54
        u'title': u'Mr.',
50 55
    }
51
    adapter.finish_create_user(idp, saml_attributes, user)
56
    user = adapter.lookup_user(idp, saml_attributes)
57
    user.refresh_from_db()
52 58
    assert user.email == 'john.doe@example.com'
53 59
    assert user.attributes.title == 'Mr.'
60
    user.delete()
61

  
62
    # on missing mandatory attribute, no user is created
54 63
    del saml_attributes['mail']
55
    with pytest.raises(ValueError):
56
        adapter.finish_create_user(idp, saml_attributes, user)
64
    assert adapter.lookup_user(idp, saml_attributes) is None
57
-