Projet

Général

Profil

0001-provisionning-log-received-provisionning-messages-an.patch

Benjamin Dauvergne, 30 septembre 2021 22:20

Télécharger (14,3 ko)

Voir les différences:

Subject: [PATCH] provisionning: log received provisionning messages and
 actions (#56907)

 .../common/management/commands/hobo_notify.py |   5 +
 hobo/multitenant/utils.py                     |  10 +-
 hobo/provisionning/middleware.py              |   6 +-
 hobo/provisionning/utils.py                   | 103 +++++++++++++-----
 tests_multitenant/test_hobo_notify.py         |   6 +-
 5 files changed, 99 insertions(+), 31 deletions(-)
hobo/agent/common/management/commands/hobo_notify.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import json
18
import logging
18 19
import os
19 20
import sys
20 21

  
......
24 25
from hobo.multitenant.middleware import TenantMiddleware
25 26
from hobo.provisionning.utils import NotificationProcessing, TryAgain
26 27

  
28
logger = logging.getLogger(__name__)
29

  
27 30

  
28 31
class Command(BaseCommand, NotificationProcessing):
29 32
    requires_system_checks = False
......
60 63
        if entity_id not in audience:
61 64
            return
62 65
        object_type = notification['objects']['@type']
66
        msg = 'received request for %sing %%d %%s objects (Celery)' % action
67
        logger.info(msg, len(notification['objects']['data']), object_type)
63 68
        for i in range(20):
64 69
            try:
65 70
                getattr(cls, 'provision_' + object_type)(
hobo/multitenant/utils.py
13 13
    logger = logging.getLogger(__name__)
14 14

  
15 15
    existing_pks = user.groups.values_list('pk', flat=True)
16
    not_found = set(uuids)
16 17
    for role in Role.objects.filter(uuid__in=uuids).exclude(pk__in=existing_pks):
18
        not_found.discard(role.uuid)
19
        if role.pk in existing_pks:
20
            continue
17 21
        user.groups.through.objects.get_or_create(group=role, user=user)
18
        logger.info(u'adding role %s to %s (%s)', role, user, user.pk)
22
        logger.info('adding role %s to %s (%s)', role, user, user.pk)
19 23
    qs = user.groups.through.objects.filter(user=user, group__role__isnull=False).exclude(
20 24
        group__role__uuid__in=uuids
21 25
    )
......
26 30
        except DatabaseError:
27 31
            pass
28 32
        else:
29
            logger.info(u'removed role %s from %s (%s)', rel.group, user, user.pk)
33
            logger.info('removed role %s from %s (%s)', rel.group, user, user.pk)
34
    for uuid in not_found:
35
        logger.warning('role %s of user %s does not exist', uuid, user)
hobo/provisionning/middleware.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import json
18
import logging
18 19
import sys
19 20

  
20 21
from django.conf import settings
......
27 28
from hobo.provisionning.utils import NotificationProcessing
28 29
from hobo.rest_authentication import PublikAuthentication, PublikAuthenticationFailed
29 30

  
31
logger = logging.getLogger(__name__)
32

  
30 33

  
31 34
class ProvisionningMiddleware(MiddlewareMixin, NotificationProcessing):
32 35
    def process_request(self, request):
......
54 57
        full = notification['full'] if 'full' in notification else False
55 58
        data = notification['objects']['data']
56 59

  
60
        msg = 'received request for %sing %%d %%s objects (HTTP)' % action
61
        logger.info(msg, len(notification['objects']['data']), object_type)
57 62
        if 'uwsgi' in sys.modules and 'sync' not in request.GET:
58 63
            from hobo.provisionning.spooler import provision
59 64

  
......
75 80
            )
76 81
        else:
77 82
            self.provision(object_type=object_type, issuer=issuer, action=action, data=data, full=full)
78

  
79 83
        return JsonResponse({'err': 0})
80 84

  
81 85
    def hobo_specific_setup(self):
hobo/provisionning/utils.py
26 26
from hobo.agent.common.models import Role
27 27
from hobo.multitenant.utils import provision_user_groups
28 28

  
29
logger = logging.getLogger(__name__)
30

  
29 31

  
30 32
class TryAgain(Exception):
31 33
    pass
32 34

  
33 35

  
36
def user_str(user):
37
    '''Compute a string representation of user'''
38
    s = ''
39
    if user.first_name or user.last_name:
40
        s += '"'
41
        if user.first_name:
42
            s += user.first_name
43
        if user.first_name and user.last_name:
44
            s += ' '
45
        if user.last_name:
46
            s += user.last_name
47
        s += '" '
48
    if user.email:
49
        s += user.email + ' '
50
    s += user.username
51
    return s
52

  
53

  
34 54
class NotificationProcessing:
35 55
    @classmethod
36 56
    def check_valid_notification(cls, notification):
......
72 92
            try:
73 93
                with atomic():
74 94
                    if action == 'provision':
95
                        new = False
96
                        updated = set()
97
                        attributes = {
98
                            'first_name': o['first_name'][:30],
99
                            'last_name': o['last_name'][:150],
100
                            'email': o['email'][:254],
101
                            'username': o['uuid'][:150],
102
                            'is_superuser': o['is_superuser'],
103
                            'is_staff': o['is_superuser'],
104
                            'is_active': o.get('is_active', True),
105
                        }
75 106
                        assert cls.check_valid_user(o)
76 107
                        try:
77 108
                            mellon_user = UserSAMLIdentifier.objects.get(
......
85 116
                                )
86 117
                            except User.DoesNotExist:
87 118
                                # temp user object
88
                                random_uid = str(random.randint(1, 10000000000000))
89
                                user = User.objects.create(username=random_uid)
119
                                user = User.objects.create(**attributes)
120
                                new = True
90 121
                            saml_issuer, created = Issuer.objects.get_or_create(entity_id=issuer)
91 122
                            mellon_user = UserSAMLIdentifier.objects.create(
92 123
                                user=user, issuer=saml_issuer, name_id=o['uuid']
93 124
                            )
94
                        user.first_name = o['first_name'][:30]
95
                        user.last_name = o['last_name'][:150]
96
                        user.email = o['email'][:254]
97
                        user.username = o['uuid'][:150]
98
                        user.is_superuser = o['is_superuser']
99
                        user.is_staff = o['is_superuser']
100
                        user.is_active = o.get('is_active', True)
101
                        user.save()
125
                        if new:
126
                            logger.info('provisionned new user %s', user_str(user))
127
                        else:
128
                            for key in attributes:
129
                                if getattr(user, key) != attributes[key]:
130
                                    setattr(user, key, attributes[key])
131
                                    updated.add(key)
132
                            if updated:
133
                                user.save()
134
                                logger.info('updated user %s(%s)', user_str(user), updated)
102 135
                        role_uuids = [role['uuid'] for role in o.get('roles', [])]
103 136
                        provision_user_groups(user, role_uuids)
104 137
                    elif action == 'deprovision':
......
106 139
                uuids.add(o['uuid'])
107 140
            except IntegrityError:
108 141
                raise TryAgain
109
        if full and action == 'provision':
110
            for usi in UserSAMLIdentifier.objects.exclude(name_id__in=uuids):
111
                usi.user.delete()
112
        elif action == 'deprovision':
113
            for user in User.objects.filter(saml_identifiers__name_id__in=uuids):
114
                user.delete()
142
        if (full and action == 'provision') or (action == 'deprovision'):
143
            if action == 'deprovision':
144
                qs = User.objects.filter(saml_identifiers__name_id__in=uuids)
145
            else:
146
                qs = User.objects.exclude(saml_identifiers__name_id__in=uuids)
147
            # retrieve users before deleting them
148
            qs = qs[:]
149
            qs.delete()
150
            for user in qs:
151
                logger.info('deprovisionning user %s', user_str(user))
115 152

  
116 153
    group_name_max_length = Group._meta.get_field('name').max_length
117 154

  
......
128 165

  
129 166
    @classmethod
130 167
    def provision_role(cls, issuer, action, data, full=False):
131
        logger = logging.getLogger(__name__)
132 168
        uuids = set()
133 169
        roles_by_uuid = dict()
134 170

  
......
145 181
            assert 'uuid' in o
146 182
            uuids.add(o['uuid'])
147 183
            if action == 'provision':
184
                created = False
185
                save = False
148 186
                assert cls.check_valid_role(o)
149 187
                role_name = cls.truncate_role_name(o['name'])
150 188
                try:
......
158 196
                                defaults={
159 197
                                    'uuid': o['uuid'],
160 198
                                    'description': o['description'],
161
                                    'details': o.get('details', u''),
199
                                    'details': o.get('details', ''),
162 200
                                    'emails': o.get('emails', []),
163 201
                                    'emails_to_members': o.get('emails_to_members', True),
164 202
                                },
165 203
                            )
166 204
                    except IntegrityError:
167 205
                        # Can happen if uuid and name already exist
168
                        logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
206
                        logger.error('cannot provision role "%s" (%s)', o['name'], o['uuid'])
169 207
                        continue
170 208
                if not created:
171
                    save = False
172 209
                    if role.name != role_name:
173 210
                        role.name = role_name
174 211
                        save = True
......
178 215
                    if role.description != o['description']:
179 216
                        role.description = o['description']
180 217
                        save = True
181
                    if role.details != o.get('details', u''):
182
                        role.details = o.get('details', u'')
218
                    if role.details != o.get('details', ''):
219
                        role.details = o.get('details', '')
183 220
                        save = True
184 221
                    if role.emails != o.get('emails', []):
185 222
                        role.emails = o.get('emails', [])
......
193 230
                                role.save()
194 231
                        except IntegrityError:
195 232
                            # Can happen if uuid and name already exist
196
                            logger.error(u'cannot provision role "%s" (%s)', o['name'], o['uuid'])
233
                            logger.error('cannot provision role "%s" (%s)', o['name'], o['uuid'])
197 234
                            continue
235
                if created:
236
                    logger.info('provisionned new role %s (%s)', o['name'], o['uuid'])
237
                if save:
238
                    logger.info('updated role %s (%s)', o['name'], o['uuid'])
198 239
        if full and action == 'provision':
199
            Role.objects.exclude(uuid__in=uuids).delete()
240
            qs = Role.objects.exclude(uuid__in=uuids)
241
            logger.info(
242
                'deprovisionning roles %s',
243
                ', '.join('%s (%s)' % (name, uuid) for name, uuid in qs.values_list('name', 'uuid')),
244
            )
245
            qs.delete()
246
            for role in qs:
247
                role.delete()
200 248
        elif action == 'deprovision':
201
            Role.objects.filter(uuid__in=uuids).delete()
249
            qs = Role.objects.filter(uuid__in=uuids)
250
            logger.info(
251
                'deprovisionning roles %s',
252
                ', '.join('%s (%s)' % (name, uuid) for name, uuid in qs.values_list('name', 'uuid')),
253
            )
254
            qs.delete()
202 255

  
203 256
    @classmethod
204 257
    def provision(cls, object_type, issuer, action, data, full):
tests_multitenant/test_hobo_notify.py
2 2

  
3 3
import logging
4 4

  
5
import pytest
5 6
from django.db import connection
6 7
from django.test.utils import CaptureQueriesContext
7
import pytest
8 8

  
9 9
pytestmark = pytest.mark.django_db
10 10

  
......
268 268
            }
269 269
            with CaptureQueriesContext(connection) as ctx:
270 270
                Command.process_notification(tenant, notification)
271
                assert len(ctx.captured_queries) == 33
271
                assert len(ctx.captured_queries) == 39
272 272
            assert Group.objects.count() == 1
273 273
            assert Role.objects.count() == 1
274 274

  
......
338 338
            }
339 339
            with CaptureQueriesContext(connection) as ctx:
340 340
                Command.process_notification(tenant, notification)
341
                assert len(ctx.captured_queries) == 15
341
                assert len(ctx.captured_queries) == 18
342 342

  
343 343
            assert Group.objects.count() == 0
344 344
            assert Role.objects.count() == 0
345
-