Projet

Général

Profil

0001-misc-monkeypatch-user-model-with-a-method-to-get-nam.patch

Frédéric Péters, 21 février 2019 12:29

Télécharger (19,6 ko)

Voir les différences:

Subject: [PATCH 1/2] misc: monkeypatch user model with a method to get name id
 (#30723)

 combo/apps/lingo/views.py          | 24 +++++++---------------
 combo/apps/newsletters/forms.py    |  5 +++--
 combo/apps/wcs/models.py           | 12 +++++++----
 combo/profile/__init__.py          |  9 +++++++++
 combo/profile/utils.py             | 32 ++++++++++++++++++++++++++++++
 combo/public/templatetags/combo.py | 10 ++++------
 combo/public/views.py              | 10 +++-------
 combo/utils/requests_wrapper.py    |  7 ++++---
 combo/utils/urls.py                |  5 +++--
 tests/test_lingo_remote_regie.py   | 12 +++--------
 tests/test_newsletters_cell.py     |  4 +---
 tests/test_profile.py              | 20 ++-----------------
 tests/test_public.py               |  2 +-
 tests/test_requests.py             | 16 ++++++---------
 tests/test_utils.py                | 16 ++++++---------
 tests/test_wcs.py                  | 30 ++++++++++------------------
 16 files changed, 102 insertions(+), 112 deletions(-)
 create mode 100644 combo/profile/utils.py
combo/apps/lingo/views.py
40 40

  
41 41
from combo.data.models import Page
42 42
from combo.utils import check_request_signature, aes_hex_decrypt, DecryptionError
43

  
44
if 'mellon' in settings.INSTALLED_APPS:
45
    from mellon.models import UserSAMLIdentifier
46
else:
47
    UserSAMLIdentifier = None
43
from combo.profile.utils import get_user_from_name_id
48 44

  
49 45
from .models import (Regie, BasketItem, Transaction, TransactionOperation,
50 46
        LingoBasketCell, SelfDeclaredInvoicePayment)
......
125 121

  
126 122
        try:
127 123
            if request.GET.get('NameId'):
128
                if UserSAMLIdentifier is None:
129
                    raise Exception('missing mellon?')
130
                try:
131
                    user = UserSAMLIdentifier.objects.get(name_id=request.GET.get('NameId')).user
132
                except UserSAMLIdentifier.DoesNotExist:
133
                    raise Exception('unknown name id')
124
                user = get_user_from_name_id(request.GET.get('NameId'))
125
                if user is None:
126
                    raise User.DoesNotExist()
134 127
            elif request.GET.get('email'):
135 128
                user = User.objects.get(email=request.GET.get('email'))
136 129
            else:
......
198 191

  
199 192
        try:
200 193
            if request.GET.get('NameId'):
201
                if UserSAMLIdentifier is None:
202
                    raise Exception('missing mellon?')
203
                try:
204
                    user = UserSAMLIdentifier.objects.get(name_id=request.GET.get('NameId')).user
205
                except UserSAMLIdentifier.DoesNotExist:
206
                    raise Exception('unknown name id')
194
                user = get_user_from_name_id(request.GET.get('NameId'))
195
                if user is None:
196
                    raise User.DoesNotExist()
207 197
            elif request.GET.get('email'):
208 198
                user = User.objects.get(email=request.GET.get('email'))
209 199
            else:
combo/apps/newsletters/forms.py
38 38
            logger.error('Error occured while getting newsletters: %r', e)
39 39
            return
40 40
        self.params = {}
41
        if hasattr(self.user, 'saml_identifiers') and self.user.saml_identifiers.exists():
42
            self.params['uuid'] = self.user.saml_identifiers.first().name_id
41
        user_name_id = self.user.get_name_id()
42
        if user_name_id:
43
            self.params['uuid'] = user_name_id
43 44

  
44 45
        # get mobile number from mellon session as it is not user attribute
45 46
        if self.request.session.get('mellon_session'):
combo/apps/wcs/models.py
313 313

  
314 314
    def get_api_url(self, context):
315 315
        user = self.get_concerned_user(context)
316
        if hasattr(user, 'saml_identifiers') and user.saml_identifiers.exists():
317
            return '/api/users/%s/forms' % user.saml_identifiers.first().name_id
316
        if user:
317
            user_name_id = user.get_name_id()
318
            if user_name_id:
319
                return '/api/users/%s/forms' % user_name_id
318 320
        return '/api/user/forms'
319 321

  
320 322
    @property
......
386 388

  
387 389
    def get_api_url(self, context):
388 390
        user = self.get_concerned_user(context)
389
        if hasattr(user, 'saml_identifiers') and user.saml_identifiers.exists():
390
            return '/api/users/%s/drafts' % user.saml_identifiers.first().name_id
391
        if user:
392
            user_name_id = user.get_name_id()
393
            if user_name_id:
394
                return '/api/users/%s/drafts' % user_name_id
391 395
        return '/api/user/drafts'
392 396

  
393 397
    def get_cell_extra_context(self, context):
combo/profile/__init__.py
21 21
from django.utils.translation import ugettext_lazy as _
22 22

  
23 23

  
24
def user_get_name_id(user):
25
    saml_identifier = user.saml_identifiers.first()
26
    if saml_identifier:
27
        return saml_identifier.name_id
28
    return None
29

  
30

  
24 31
class AppConfig(django.apps.AppConfig):
25 32
    name = 'combo.profile'
26 33
    verbose_name = _('Profile')
......
28 35
    def ready(self):
29 36
        from combo.apps.search import engines
30 37
        engines.register(self.get_search_engines)
38
        from django.contrib.auth import get_user_model
39
        get_user_model().add_to_class('get_name_id', user_get_name_id)
31 40

  
32 41
    def get_search_engines(self):
33 42
        from combo.data.models import Page
combo/profile/utils.py
1
# combo - content management system
2
# Copyright (C) 2014-2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.conf import settings
18

  
19

  
20
if 'mellon' in settings.INSTALLED_APPS:
21
    from mellon.models import UserSAMLIdentifier
22
else:
23
    UserSAMLIdentifier = None
24

  
25

  
26
def get_user_from_name_id(name_id):
27
    if not UserSAMLIdentifier:
28
        return None
29
    try:
30
        return UserSAMLIdentifier.objects.get(name_id=name_id).user
31
    except UserSAMLIdentifier.DoesNotExist:
32
        return None
combo/public/templatetags/combo.py
34 34
from combo.utils import NothingInCacheException, flatten_context
35 35
from combo.apps.dashboard.models import DashboardCell, Tile
36 36

  
37
if 'mellon' in settings.INSTALLED_APPS:
38
    from mellon.models import UserSAMLIdentifier
39

  
40 37
register = template.Library()
41 38

  
42 39
def skeleton_text(context, placeholder_name, content=''):
......
248 245

  
249 246
@register.filter
250 247
def name_id(user):
251
    saml_id = UserSAMLIdentifier.objects.filter(user=user).last()
252
    if saml_id:
253
        return saml_id.name_id
248
    if user:
249
        user_name_id = user.get_name_id()
250
        if user_name_id:
251
            return user_name_id
254 252
    # it is important to raise this so get_templated_url is aborted and no call
255 253
    # is tried with a missing user argument.
256 254
    raise VariableDoesNotExist('name_id')
combo/public/views.py
45 45

  
46 46
if 'mellon' in settings.INSTALLED_APPS:
47 47
    from mellon.utils import get_idps
48
    from mellon.models import UserSAMLIdentifier
49 48
else:
50 49
    get_idps = lambda: []
51
    UserSAMLIdentifier = None
52 50

  
53 51
from combo.data.models import (CellBase, PostException, Page, Redirect,
54 52
        ParentContentCell, TextCell, PageSnapshot)
55 53
from combo.profile.models import Profile
54
from combo.profile.utils import get_user_from_name_id
56 55
from combo.apps.search.models import SearchCell
57 56
from combo import utils
58 57

  
......
81 80
            ctx['selected_user'] = User.objects.get(id=ctx['user_id'])
82 81
        except (User.DoesNotExist, ValueError):
83 82
            pass
84
    if 'name_id' in ctx and UserSAMLIdentifier:
85
        try:
86
            ctx['selected_user'] = UserSAMLIdentifier.objects.get(name_id=ctx['name_id']).user
87
        except UserSAMLIdentifier.DoesNotExist:
88
            pass
83
    if 'name_id' in ctx:
84
        ctx['selected_user'] = get_user_from_name_id(ctx['name_id'])
89 85

  
90 86
@csrf_exempt
91 87
def ajax_page_cell(request, page_pk, cell_reference):
combo/utils/requests_wrapper.py
79 79
            else:
80 80
                query_params = {}
81 81
                if federation_key == 'nameid':
82
                    query_params['NameID'] = user.saml_identifiers.first().name_id
82
                    query_params['NameID'] = user.get_name_id()
83 83
                elif federation_key == 'email':
84 84
                    query_params['email'] = user.email
85 85
                else: # 'auto'
86
                    if hasattr(user, 'saml_identifiers') and user.saml_identifiers.exists():
87
                        query_params['NameID'] = user.saml_identifiers.first().name_id
86
                    user_name_id = user.get_name_id()
87
                    if user_name_id:
88
                        query_params['NameID'] = user_name_id
88 89
                    else:
89 90
                        query_params['email'] = user.email
90 91

  
combo/utils/urls.py
42 42
        user = getattr(context.get('request'), 'user', None)
43 43
        if user and user.is_authenticated():
44 44
            template_vars['user_email'] = quote(user.email)
45
            if hasattr(user, 'saml_identifiers') and user.saml_identifiers.exists():
46
                template_vars['user_nameid'] = quote(user.saml_identifiers.first().name_id)
45
            user_nameid = user.get_name_id()
46
            if user_nameid:
47
                template_vars['user_nameid'] = quote(user_nameid)
47 48
    template_vars.update(settings.TEMPLATE_VARS)
48 49
    if '{{' in url or '{%' in url:  # Django template
49 50
        try:
tests/test_lingo_remote_regie.py
75 75
    def is_authenticated(self):
76 76
        return True
77 77

  
78
    def __init__(self):
79
        class MockSAMLUsers(object):
80
            def exists(self):
81
                return True
82
            def first(self):
83
                class MockSAMLUser(object):
84
                    name_id = 'r2d2'
85
                return MockSAMLUser()
86
        self.saml_identifiers = MockSAMLUsers()
78
    def get_name_id(self):
79
        return 'r2d2'
80

  
87 81

  
88 82
@mock.patch('combo.utils.requests_wrapper.RequestsSession.request')
89 83
def test_remote_regie_active_invoices_cell(mock_request, remote_regie):
tests/test_newsletters_cell.py
223 223

  
224 224
    fake_saml_request = mock.Mock()
225 225
    fake_saml_request.user = mock.Mock(email=USER_EMAIL)
226
    fake_saml_request.user.saml_identifiers = mock.Mock()
227
    fake_saml_request.user.saml_identifiers.exists.return_value = True
228
    fake_saml_request.user.saml_identifiers.first.return_value = mock.Mock(name_id='nameid')
226
    fake_saml_request.user.get_name_id.return_value = 'nameid'
229 227
    fake_saml_request.session = {'mellon_session': {'mobile': '0607080900'}}
230 228

  
231 229
    form = NewslettersManageForm(instance=cell, request=fake_saml_request)
tests/test_profile.py
13 13

  
14 14
@override_settings(
15 15
        KNOWN_SERVICES={'authentic': {'idp': {'title': 'IdP', 'url': 'http://example.org/'}}})
16
@mock.patch('combo.public.templatetags.combo.UserSAMLIdentifier')
17 16
@mock.patch('combo.utils.requests.get')
18
def test_profile_cell(requests_get, user_saml, app, admin_user):
17
def test_profile_cell(requests_get, app, admin_user):
19 18
    page = Page()
20 19
    page.save()
21 20

  
......
28 27
            json=lambda: data,
29 28
            status_code=200)
30 29

  
31
    def filter_mock(user=None):
32
        assert user is admin_user
33
        return mock.Mock(last=lambda: mock.Mock(name_id='123456'))
34

  
35
    mocked_objects = mock.Mock()
36
    mocked_objects.filter = mock.Mock(side_effect=filter_mock)
37
    user_saml.objects = mocked_objects
30
    admin_user.get_name_id = lambda: '123456'
38 31

  
39 32
    context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
40 33
    assert context['profile_fields']['first_name']['value'] == 'Foo'
41 34
    assert context['profile_fields']['birthdate']['value'] == datetime.date(2018, 8, 10)
42 35
    assert requests_get.call_args[0][0] == 'http://example.org/api/users/123456/'
43

  
44
    def filter_mock_missing(user=None):
45
        return mock.Mock(last=lambda: None)
46

  
47
    mocked_objects.filter = mock.Mock(side_effect=filter_mock_missing)
48

  
49
    context = cell.get_cell_extra_context({'synchronous': True, 'selected_user': admin_user})
50
    assert context['error'] == 'unknown user'
51
    assert requests_get.call_count == 1  # no new call was made
tests/test_public.py
809 809
        assert 'XXYY' in resp.text
810 810

  
811 811
        # custom behaviour for <name_id>, it will add the SAML user to context
812
        with mock.patch('combo.public.views.UserSAMLIdentifier') as user_saml:
812
        with mock.patch('combo.profile.utils.UserSAMLIdentifier') as user_saml:
813 813
            class DoesNotExist(Exception):
814 814
                pass
815 815
            user_saml.DoesNotExist = DoesNotExist
tests/test_requests.py
8 8

  
9 9
from combo.utils import requests, check_query, NothingInCacheException
10 10

  
11
class MockSAMLUser(object):
12
    name_id = 'r2d2'
13

  
14 11
class MockUser(object):
15 12
    email = 'foo@example.net'
16 13
    def is_authenticated(self):
17 14
        return True
18 15

  
16
    def get_name_id(self):
17
        if self.samlized:
18
            return 'r2d2'
19
        return None
20

  
19 21
    def __init__(self, samlized=True):
20
        class MockSAMLUsers(object):
21
            def exists(self):
22
                return True
23
            def first(self):
24
                return MockSAMLUser()
25
        if samlized:
26
            self.saml_identifiers = MockSAMLUsers()
22
        self.samlized = samlized
27 23

  
28 24

  
29 25
def test_nosign():
tests/test_utils.py
9 9
from django.contrib.auth.models import AnonymousUser
10 10

  
11 11

  
12
class MockSAMLUser(object):
13
    name_id = 'r2&d2'
14

  
15 12
class MockUser(object):
16 13
    email = 'foo=3@example.net'
17 14
    def is_authenticated(self):
18 15
        return True
19 16

  
20 17
    def __init__(self, samlized=True):
21
        class MockSAMLUsers(object):
22
            def exists(self):
23
                return True
24
            def first(self):
25
                return MockSAMLUser()
26
        if samlized:
27
            self.saml_identifiers = MockSAMLUsers()
18
        self.samlized = samlized
19

  
20
    def get_name_id(self):
21
        if self.samlized:
22
            return 'r2&d2'
23
        return None
28 24

  
29 25

  
30 26
def test_crypto_url():
tests/test_wcs.py
161 161
WCS_DIR = tempfile.mkdtemp()
162 162
WCS_PID = None
163 163

  
164

  
165
class MockUser(object):
166
    email = 'foo@example.net'
167
    def is_authenticated(self):
168
        return True
169
    def get_name_id(self):
170
        return None
171

  
164 172
def run_wcs_script(script, hostname):
165 173
    script_path = os.path.join(WCS_DIR, script + '.py')
166 174
    fd = open(script_path, 'w')
......
345 353
    cell = WcsCurrentFormsCell(page=page, placeholder='content', order=0)
346 354
    cell.save()
347 355

  
348
    class MockUser(object):
349
        email = 'foo@example.net'
350
        def is_authenticated(self):
351
            return True
352 356
    context['request'].user = MockUser()
353 357

  
354 358
    # query should fail as nothing is cached
......
412 416
    cell.wcs_site = 'default'
413 417
    cell.save()
414 418

  
415
    class MockUser(object):
416
        email = 'foo@example.net'
417
        def is_authenticated(self):
418
            return True
419 419
    context['request'].user = MockUser()
420 420

  
421 421
    # query should fail as nothing is cached
......
506 506
    cell.save()
507 507
    context['synchronous'] = True # to get fresh content
508 508

  
509
    class MockUser(object):
510
        email = 'foo@example.net'
511
        def is_authenticated(self):
512
            return True
513 509
    context['request'].user = MockUser()
514 510

  
515 511
    # default is to get current forms from all wcs sites
......
695 691
    result = cell.render(context)
696 692
    assert '/backoffice/submission/a-private-form/' not in result
697 693

  
698
    class MockUser(object):
699
        email = 'foo@example.net'
700
        def is_authenticated(self):
701
            return True
702 694
    context['request'].user = MockUser()
703 695

  
704 696
    result = cell.render(context)
705 697
    assert '/backoffice/submission/a-private-form/' not in result
706 698

  
707
    class MockUser(object):
699
    class MockUser2(MockUser):
708 700
        email = 'foo2@example.net'
709
        def is_authenticated(self):
710
            return True
711
    context['request'].user = MockUser()
701
    context['request'].user = MockUser2()
712 702

  
713 703
    result = cell.render(context)
714 704
    assert '/backoffice/submission/a-private-form/' in result
715
-