0001-provisionning-log-received-provisionning-messages-an.patch
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 |
- |