Projet

Général

Profil

0001-fix-concurrency-error-when-creating-new-users-fixes-.patch

Benjamin Dauvergne, 12 février 2016 17:48

Télécharger (4,78 ko)

Voir les différences:

Subject: [PATCH] fix concurrency error when creating new users (fixes #9965)

UserSAMLIdentifier is retrieved using get_or_create() first, and if is new
we proceed with the creation of the new user, otherwise we delete the temporaru
user we created use the one attached to the existing UserSAMLIdentifier.
 mellon/adapters.py            | 23 +++++++++++++----------
 tests/conftest.py             | 12 ++++++++++++
 tests/test_default_adapter.py | 21 +++++++++++++++++++++
 testsettings.py               |  3 +++
 4 files changed, 49 insertions(+), 10 deletions(-)
 create mode 100644 tests/conftest.py
mellon/adapters.py
1 1
import logging
2
import uuid
2 3

  
3 4
from django.core.exceptions import PermissionDenied
4 5
from django.contrib import auth
......
53 54
        issuer = saml_attributes['issuer']
54 55
        try:
55 56
            return User.objects.get(saml_identifiers__name_id=name_id,
56
                    saml_identifiers__issuer=issuer)
57
                                    saml_identifiers__issuer=issuer)
57 58
        except User.DoesNotExist:
58 59
            if not utils.get_setting(idp, 'PROVISION'):
60
                self.logger.warning('provisionning disabled, login refused')
59 61
                return None
60 62
            username = self.format_username(idp, saml_attributes)
61 63
            if not username:
64
                self.logger.warning('could not build a username, login refused')
62 65
                return None
63
            user = User(username=username)
64
            user.save()
65
            self.provision_name_id(user, idp, saml_attributes)
66
            user = User.objects.create(username=uuid.uuid4().hex[:30])
67
            saml_id, created = models.UserSAMLIdentifier.objects.get_or_create(
68
                name_id=name_id, issuer=issuer, defaults={'user': user})
69
            if created:
70
                user.username = username
71
                user.save()
72
            else:
73
                user.delete()
74
                user = saml_id.user
66 75
        return user
67 76

  
68 77
    def provision(self, user, idp, saml_attributes):
......
70 79
        self.provision_superuser(user, idp, saml_attributes)
71 80
        self.provision_groups(user, idp, saml_attributes)
72 81

  
73
    def provision_name_id(self, user, idp, saml_attributes):
74
        models.UserSAMLIdentifier.objects.get_or_create(
75
                user=user,
76
                issuer=saml_attributes['issuer'],
77
                name_id=saml_attributes['name_id_content'])
78

  
79 82
    def provision_attribute(self, user, idp, saml_attributes):
80 83
        realm = utils.get_setting(idp, 'REALM')
81 84
        attribute_mapping = utils.get_setting(idp, 'ATTRIBUTE_MAPPING')
tests/conftest.py
1
import pytest
2

  
3

  
4
@pytest.fixture
5
def concurrency(settings):
6
    '''Select a level of concurrency based on the db, sqlite3 is less robust
7
       thant postgres due to its transaction lock timeout of 5 seconds.
8
    '''
9
    if 'sqlite' in settings.DATABASES['default']['ENGINE']:
10
        return 20
11
    else:
12
        return 100
tests/test_default_adapter.py
1
import threading
1 2
import pytest
2 3

  
3 4
from django.conf import settings
......
45 46
    assert user is None
46 47
    assert User.objects.count() == 0
47 48

  
49

  
50
def test_lookup_user_transaction(transactional_db, concurrency):
51
    adapter = DefaultAdapter()
52
    N = 30
53
    def map_threads(f, l):
54
        threads = []
55
        for i in l:
56
            threads.append(threading.Thread(target=f, args=(i,)))
57
            threads[-1].start()
58
        for thread in threads:
59
            thread.join()
60
    users = []
61

  
62
    def f(i):
63
        users.append(adapter.lookup_user(idp, saml_attributes))
64
    map_threads(f, range(concurrency))
65
    assert len(users) == concurrency
66
    assert len(set(user.pk for user in users)) == 1
67

  
68

  
48 69
def test_provision(settings):
49 70
    settings.MELLON_GROUP_ATTRIBUTE = 'group'
50 71
    User = auth.get_user_model()
testsettings.py
4 4
    'default': {
5 5
        'ENGINE': 'django.db.backends.sqlite3',
6 6
        'NAME': 'mellon.sqlite3',
7
        'TEST': {
8
            'NAME': 'mellon-test.sqlite',
9
        },
7 10
    }
8 11
}
9 12
DEBUG = True
10
-