0001-send-provisionning-messages-after-request-treatment-.patch
debian/debian_config_common.py | ||
---|---|---|
209 | 209 |
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( |
210 | 210 |
'mellon.middleware.PassiveAuthenticationMiddleware', |
211 | 211 |
) |
212 |
else: |
|
213 |
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( |
|
214 |
'hobo.agent.authentic2.middleware.ProvisionningMiddleware', |
|
215 |
) |
|
212 | 216 | |
213 | 217 |
TENANT_SETTINGS_LOADERS = ( |
214 | 218 |
'hobo.multitenant.settings_loaders.TemplateVars', |
hobo/agent/authentic2/apps.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 json |
|
18 |
from urlparse import urljoin |
|
19 | ||
20 | 17 |
from django.apps import AppConfig |
21 |
from django.db.models.signals import post_save, post_delete, pre_delete, m2m_changed |
|
22 |
from django.conf import settings |
|
23 |
from django.contrib.auth import get_user_model |
|
24 |
from django.db import connection |
|
25 |
from django.core.urlresolvers import reverse |
|
26 | ||
27 |
from django_rbac.utils import get_role_model |
|
28 | ||
29 |
from hobo.agent.common import notify_agents |
|
30 |
from authentic2.models import AttributeValue |
|
31 |
from authentic2.saml.models import LibertyProvider |
|
32 |
from authentic2.a2_rbac.models import OrganizationalUnit |
|
33 | ||
34 | ||
35 |
def get_ou(role_or_through): |
|
36 |
if hasattr(role_or_through, 'ou_id'): |
|
37 |
return role_or_through.ou |
|
38 |
else: |
|
39 |
return role_or_through.role.ou |
|
40 | ||
41 | ||
42 |
def get_audience(role_or_through): |
|
43 |
ou = get_ou(role_or_through) |
|
44 |
if ou: |
|
45 |
qs = LibertyProvider.objects.filter(ou=ou) |
|
46 |
else: |
|
47 |
qs = LibertyProvider.objects.filter(ou__isnull=True) |
|
48 |
return [(service, service.entity_id) for service in qs] |
|
49 | ||
50 | ||
51 |
def get_related_roles(role_or_through): |
|
52 |
ou = get_ou(role_or_through) |
|
53 |
Role = get_role_model() |
|
54 |
qs = Role.objects.filter(admin_scope_id__isnull=True) \ |
|
55 |
.prefetch_related('attributes') |
|
56 |
if ou: |
|
57 |
qs = qs.filter(ou=ou) |
|
58 |
else: |
|
59 |
qs = qs.filter(ou__isnull=True) |
|
60 |
for role in qs: |
|
61 |
role.emails = [] |
|
62 |
role.emails_to_members = False |
|
63 |
role.details = u'' |
|
64 |
for attribute in role.attributes.all(): |
|
65 |
if (attribute.name in ('emails', 'emails_to_members', 'details') |
|
66 |
and attribute.kind == 'json'): |
|
67 |
setattr(role, attribute.name, json.loads(attribute.value)) |
|
68 |
return qs |
|
69 | ||
70 | ||
71 |
def notify_roles(sender, instance, **kwargs): |
|
72 |
if not getattr(settings, 'HOBO_ROLE_EXPORT', True): |
|
73 |
return |
|
74 |
if instance.slug.startswith('_'): |
|
75 |
return |
|
76 |
try: |
|
77 |
notify_agents({ |
|
78 |
'@type': 'provision', |
|
79 |
'audience': [audience for service, audience in get_audience(instance)], |
|
80 |
'full': True, |
|
81 |
'objects': { |
|
82 |
'@type': 'role', |
|
83 |
'data': [ |
|
84 |
{ |
|
85 |
'uuid': role.uuid, |
|
86 |
'name': role.name, |
|
87 |
'slug': role.slug, |
|
88 |
'description': role.description, |
|
89 |
'details': role.details, |
|
90 |
'emails': role.emails, |
|
91 |
'emails_to_members': role.emails_to_members, |
|
92 |
} for role in get_related_roles(instance) |
|
93 |
], |
|
94 |
} |
|
95 |
}) |
|
96 |
except OrganizationalUnit.DoesNotExist: |
|
97 |
pass |
|
98 | ||
99 | ||
100 |
def get_entity_id(): |
|
101 |
tenant = getattr(connection, 'tenant', None) |
|
102 |
assert tenant |
|
103 |
base_url = tenant.get_base_url() |
|
104 |
return urljoin(base_url, reverse('a2-idp-saml-metadata')) |
|
105 | ||
106 | ||
107 |
def provision_user(sender, instance, **kwargs): |
|
108 |
User = get_user_model() |
|
109 |
if not isinstance(instance, User): |
|
110 |
return |
|
111 |
if not getattr(settings, 'HOBO_ROLE_EXPORT', True): |
|
112 |
return |
|
113 |
data = {} |
|
114 |
for av in AttributeValue.objects.with_owner(instance): |
|
115 |
data[str(av.attribute.name)] = av.to_python() |
|
116 | ||
117 |
roles = instance.roles_and_parents() \ |
|
118 |
.prefetch_related('attributes') |
|
119 |
is_superuser = instance.is_superuser |
|
120 |
data.update({ |
|
121 |
'uuid': instance.uuid, |
|
122 |
'username': instance.username, |
|
123 |
'first_name': instance.first_name, |
|
124 |
'last_name': instance.last_name, |
|
125 |
'email': instance.email, |
|
126 |
'roles': [ |
|
127 |
{ |
|
128 |
'uuid': role.uuid, |
|
129 |
'name': role.name, |
|
130 |
'slug': role.slug, |
|
131 |
} for role in roles], |
|
132 |
}) |
|
133 | ||
134 |
for service, audience in get_audience(instance): |
|
135 |
role_is_superuser = False |
|
136 |
for role in roles: |
|
137 |
if role.service_id != service.pk: |
|
138 |
continue |
|
139 |
for attribute in role.attributes.all(): |
|
140 |
if attribute.name == 'is_superuser' and attribute.value == 'true': |
|
141 |
role_is_superuser = True |
|
142 |
data['is_superuser'] = is_superuser or role_is_superuser |
|
143 |
notify_agents({ |
|
144 |
'@type': 'provision', |
|
145 |
'issuer': unicode(get_entity_id()), |
|
146 |
'audience': [audience], |
|
147 |
'full': False, |
|
148 |
'objects': { |
|
149 |
'@type': 'user', |
|
150 |
'data': [data], |
|
151 |
} |
|
152 |
}) |
|
153 | ||
154 | ||
155 |
def deprovision_user(sender, instance, **kwargs): |
|
156 |
User = get_user_model() |
|
157 |
if not isinstance(instance, User): |
|
158 |
return |
|
159 |
if not getattr(settings, 'HOBO_ROLE_EXPORT', True): |
|
160 |
return |
|
161 |
notify_agents({ |
|
162 |
'@type': 'deprovision', |
|
163 |
'issuer': unicode(get_entity_id()), |
|
164 |
'audience': [audience for service, audience in get_audience(instance)], |
|
165 |
'full': False, |
|
166 |
'objects': { |
|
167 |
'@type': 'user', |
|
168 |
'data': [ |
|
169 |
{ |
|
170 |
'uuid': instance.uuid, |
|
171 |
} |
|
172 |
], |
|
173 |
} |
|
174 |
}) |
|
175 | ||
176 | ||
177 |
def provision_user_on_role_change(sender, action, instance, model, pk_set, |
|
178 |
reverse, **kwargs): |
|
179 |
if not action.startswith('post'): |
|
180 |
return |
|
181 |
if action.endswith('_clear'): |
|
182 |
return |
|
183 |
if reverse: |
|
184 |
provision_user(sender, instance, **kwargs) |
|
185 |
else: |
|
186 |
for user in model.objects.filter(pk__in=pk_set): |
|
187 |
provision_user(sender, user, **kwargs) |
|
188 | ||
189 | ||
190 |
def provision_user_on_attribute_value_save(sender, instance, **kwargs): |
|
191 |
User = get_user_model() |
|
192 |
if not isinstance(instance.owner, User): |
|
193 |
return |
|
194 |
provision_user(User, instance.owner) |
|
195 | ||
196 | ||
197 |
def provision_user_on_attribute_value_delete(sender, instance, **kwargs): |
|
198 |
User = get_user_model() |
|
199 |
if not isinstance(instance.owner, User): |
|
200 |
return |
|
201 |
provision_user(User, instance.owner) |
|
18 |
from django.db.models.signals import pre_save, pre_delete, m2m_changed, post_save |
|
202 | 19 | |
203 | 20 | |
204 | 21 |
class Authentic2AgentConfig(AppConfig): |
... | ... | |
207 | 24 |
verbose_name = 'Authentic2 Agent' |
208 | 25 | |
209 | 26 |
def ready(self): |
210 |
Role = get_role_model() |
|
211 |
post_save.connect(notify_roles, sender=Role) |
|
212 |
post_delete.connect(notify_roles, sender=Role) |
|
213 |
post_save.connect(notify_roles, Role) |
|
214 |
post_delete.connect(notify_roles, Role) |
|
215 |
post_save.connect(notify_roles, Role.members.through) |
|
216 |
post_delete.connect(notify_roles, Role.members.through) |
|
217 |
post_save.connect(provision_user) |
|
218 |
pre_delete.connect(deprovision_user) |
|
219 |
post_save.connect(provision_user_on_attribute_value_save, sender=AttributeValue) |
|
220 |
post_delete.connect(provision_user_on_attribute_value_delete, sender=AttributeValue) |
|
221 |
m2m_changed.connect(provision_user_on_role_change, |
|
222 |
sender=Role.members.through) |
|
223 |
settings.A2_MANAGER_ROLE_FORM_CLASS = \ |
|
224 |
'hobo.agent.authentic2.role_forms.RoleForm' |
|
27 |
from . import provisionning |
|
28 | ||
29 |
engine = provisionning.Provisionning() |
|
30 |
pre_save.connect(engine.pre_save) |
|
31 |
post_save.connect(engine.post_save) |
|
32 |
pre_delete.connect(engine.pre_delete) |
|
33 |
m2m_changed.connect(engine.m2m_changed) |
|
34 |
provisionning.provisionning = engine |
hobo/agent/authentic2/middleware.py | ||
---|---|---|
1 |
from .provisionning import provisionning |
|
2 | ||
3 | ||
4 |
class ProvisionningMiddleware(object): |
|
5 |
def process_request(self, request): |
|
6 |
provisionning.start() |
|
7 | ||
8 |
def process_exception(self, request, exception): |
|
9 |
provisionning.clean() |
|
10 | ||
11 |
def process_response(self, request, response): |
|
12 |
provisionning.provision() |
|
13 |
hobo/agent/authentic2/provisionning.py | ||
---|---|---|
1 |
import json |
|
2 |
from urlparse import urljoin |
|
3 |
import threading |
|
4 |
import copy |
|
5 |
import logging |
|
6 | ||
7 |
from django.contrib.auth import get_user_model |
|
8 |
from django.db import connection |
|
9 |
from django.core.urlresolvers import reverse |
|
10 |
from django.conf import settings |
|
11 | ||
12 |
from django_rbac.utils import get_role_model, get_ou_model, get_role_parenting_model |
|
13 |
from hobo.agent.common import notify_agents |
|
14 |
from authentic2.saml.models import LibertyProvider |
|
15 |
from authentic2.a2_rbac.models import RoleAttribute |
|
16 |
from authentic2.models import AttributeValue |
|
17 | ||
18 | ||
19 |
class Provisionning(object): |
|
20 |
local = threading.local() |
|
21 |
threads = set() |
|
22 | ||
23 |
def __init__(self): |
|
24 |
self.User = get_user_model() |
|
25 |
self.Role = get_role_model() |
|
26 |
self.OU = get_ou_model() |
|
27 |
self.RoleParenting = get_role_parenting_model() |
|
28 |
self.logger = logging.getLogger(__name__) |
|
29 | ||
30 |
def start(self): |
|
31 |
self.local.saved = {} |
|
32 |
self.local.deleted = {} |
|
33 | ||
34 |
def clean(self): |
|
35 |
if hasattr(self.local, 'saved'): |
|
36 |
del self.local.saved |
|
37 |
if hasattr(self.local, 'deleted'): |
|
38 |
del self.local.deleted |
|
39 | ||
40 |
def saved(self, *args): |
|
41 |
if not hasattr(self.local, 'saved'): |
|
42 |
return |
|
43 | ||
44 |
for instance in args: |
|
45 |
klass = self.User if isinstance(instance, self.User) else self.Role |
|
46 |
self.local.saved.setdefault(klass, set()).add(instance) |
|
47 | ||
48 |
def deleted(self, *args): |
|
49 |
if not hasattr(self.local, 'saved'): |
|
50 |
return |
|
51 | ||
52 |
for instance in args: |
|
53 |
klass = self.User if isinstance(instance, self.User) else self.Role |
|
54 |
self.local.deleted.setdefault(klass, set()).add(instance) |
|
55 |
self.local.saved.get(klass, set()).discard(instance) |
|
56 | ||
57 |
def resolve_ou(self, instances, ous): |
|
58 |
for instance in instances: |
|
59 |
if instance.ou_id in ous: |
|
60 |
instance.ou = ous[instance.ou_id] |
|
61 | ||
62 |
def notify_users(self, ous, users, mode='provision'): |
|
63 |
self.resolve_ou(users, ous) |
|
64 | ||
65 |
ous = {} |
|
66 |
for user in users: |
|
67 |
ous.setdefault(user.ou, set()).add(user) |
|
68 | ||
69 |
def user_to_json(service, user): |
|
70 |
data = {} |
|
71 |
roles = user.roles_and_parents().prefetch_related('attributes') |
|
72 |
data.update({ |
|
73 |
'uuid': user.uuid, |
|
74 |
'username': user.username, |
|
75 |
'first_name': user.first_name, |
|
76 |
'last_name': user.last_name, |
|
77 |
'email': user.email, |
|
78 |
'roles': [ |
|
79 |
{ |
|
80 |
'uuid': role.uuid, |
|
81 |
'name': role.name, |
|
82 |
'slug': role.slug, |
|
83 |
} for role in roles], |
|
84 |
}) |
|
85 |
for av in AttributeValue.objects.with_owner(user): |
|
86 |
data[str(av.attribute.name)] = av.to_python() |
|
87 |
roles = user.roles_and_parents().prefetch_related('attributes') |
|
88 |
# check if user is superuser through a role |
|
89 |
role_is_superuser = False |
|
90 |
for role in roles: |
|
91 |
if role.service_id != service.pk: |
|
92 |
continue |
|
93 |
for attribute in role.attributes.all(): |
|
94 |
if attribute.name == 'is_superuser' and attribute.value == 'true': |
|
95 |
role_is_superuser = True |
|
96 |
data['is_superuser'] = user.is_superuser or role_is_superuser |
|
97 |
return data |
|
98 | ||
99 |
issuer = unicode(self.get_entity_id()) |
|
100 |
if mode == 'provision': |
|
101 |
for ou, users in ous.iteritems(): |
|
102 |
for service, audience in self.get_audience(user): |
|
103 |
self.logger.info(u'provisionning user %s to %s', user, audience) |
|
104 |
notify_agents({ |
|
105 |
'@type': 'provision', |
|
106 |
'issuer': issuer, |
|
107 |
'audience': [audience], |
|
108 |
'full': False, |
|
109 |
'objects': { |
|
110 |
'@type': 'user', |
|
111 |
'data': [user_to_json(service, user)], |
|
112 |
} |
|
113 |
}) |
|
114 |
else: |
|
115 |
for ou, users in ous.iteritems(): |
|
116 |
audience = [audience for s, audience in self.get_audience(user)] |
|
117 |
self.logger.info(u'deprovisionning users %s from %s', users, audience) |
|
118 |
notify_agents({ |
|
119 |
'@type': 'deprovision', |
|
120 |
'issuer': issuer, |
|
121 |
'audience': audience, |
|
122 |
'full': False, |
|
123 |
'objects': { |
|
124 |
'@type': 'user', |
|
125 |
'data': [{ |
|
126 |
'uuid': user.uuid, |
|
127 |
} for user in users] |
|
128 |
} |
|
129 |
}) |
|
130 | ||
131 |
def notify_roles(self, ous, roles, mode='provision', full=False): |
|
132 |
roles = set([role for role in roles if not role.slug.startswith('_')]) |
|
133 |
if mode == 'provision': |
|
134 |
self.complete_roles(roles) |
|
135 | ||
136 |
if not roles: |
|
137 |
return |
|
138 | ||
139 |
self.resolve_ou(roles, ous) |
|
140 |
ous = {} |
|
141 |
for role in roles: |
|
142 |
ous.setdefault(role.ou, []).append(role) |
|
143 | ||
144 |
def helper(ou, roles): |
|
145 |
if mode == 'provision': |
|
146 |
data = [ |
|
147 |
{ |
|
148 |
'uuid': role.uuid, |
|
149 |
'name': role.name, |
|
150 |
'slug': role.slug, |
|
151 |
'description': role.description, |
|
152 |
'details': role.details, |
|
153 |
'emails': role.emails, |
|
154 |
'emails_to_members': role.emails_to_members, |
|
155 |
} for role in roles |
|
156 |
] |
|
157 |
else: |
|
158 |
data = [ |
|
159 |
{ |
|
160 |
'uuid': role.uuid, |
|
161 |
} for role in roles |
|
162 |
] |
|
163 | ||
164 |
audience = [entity_id for service, entity_id in self.get_audience(ou)] |
|
165 |
self.logger.info(u'%sning roles %s to %s', mode, role, audience) |
|
166 |
notify_agents({ |
|
167 |
'@type': mode, |
|
168 |
'audience': audience, |
|
169 |
'full': full, |
|
170 |
'objects': { |
|
171 |
'@type': 'role', |
|
172 |
'data': data, |
|
173 |
} |
|
174 |
}) |
|
175 | ||
176 |
global_roles = set(ous.get(None, [])) |
|
177 |
for ou, ou_roles in ous.iteritems(): |
|
178 |
sent_roles = set(ou_roles) | global_roles |
|
179 |
helper(ou, sent_roles) |
|
180 | ||
181 |
def provision(self): |
|
182 |
if not getattr(settings, 'HOBO_ROLE_EXPORT', True): |
|
183 |
return |
|
184 |
# exit early if not started |
|
185 |
if not hasattr(self.local, 'saved') or not hasattr(self.local, 'deleted'): |
|
186 |
return |
|
187 | ||
188 |
t = threading.Thread(target=self.do_provision, kwargs={ |
|
189 |
'saved': getattr(self.local, 'saved', {}), |
|
190 |
'deleted': getattr(self.local, 'deleted', {}), |
|
191 |
}) |
|
192 |
t.start() |
|
193 |
self.threads.add(t) |
|
194 |
self.clean() |
|
195 | ||
196 |
def do_provision(self, saved, deleted, thread=None): |
|
197 |
try: |
|
198 |
ous = {ou.id: ou for ou in self.OU.objects.all()} |
|
199 |
self.notify_roles(ous, saved.get(self.Role, [])) |
|
200 |
self.notify_roles(ous, deleted.get(self.Role, []), mode='deprovision') |
|
201 |
self.notify_users(ous, saved.get(self.User, [])) |
|
202 |
self.notify_users(ous, deleted.get(self.User, []), mode='deprovision') |
|
203 |
except Exception: |
|
204 |
# last step, clear everything |
|
205 |
self.logger.exception(u'error in provisionning thread') |
|
206 |
finally: |
|
207 |
self.threads.discard(threading.current_thread()) |
|
208 | ||
209 |
def wait(self): |
|
210 |
for thread in list(self.threads): |
|
211 |
thread.join() |
|
212 | ||
213 |
def __enter__(self): |
|
214 |
self.start() |
|
215 | ||
216 |
def __exit__(self, exc_type, exc_value, exc_tb): |
|
217 |
if exc_type is None: |
|
218 |
self.provision() |
|
219 |
self.wait() |
|
220 |
else: |
|
221 |
self.clean() |
|
222 | ||
223 |
def get_audience(self, ou): |
|
224 |
if ou: |
|
225 |
qs = LibertyProvider.objects.filter(ou=ou) |
|
226 |
else: |
|
227 |
qs = LibertyProvider.objects.filter(ou__isnull=True) |
|
228 |
return [(service, service.entity_id) for service in qs] |
|
229 | ||
230 |
def complete_roles(self, roles): |
|
231 |
for role in roles: |
|
232 |
role.emails = [] |
|
233 |
role.emails_to_members = False |
|
234 |
role.details = u'' |
|
235 |
for attribute in role.attributes.all(): |
|
236 |
if (attribute.name in ('emails', 'emails_to_members', 'details') |
|
237 |
and attribute.kind == 'json'): |
|
238 |
setattr(role, attribute.name, json.loads(attribute.value)) |
|
239 | ||
240 |
def get_entity_id(self): |
|
241 |
tenant = getattr(connection, 'tenant', None) |
|
242 |
assert tenant |
|
243 |
base_url = tenant.get_base_url() |
|
244 |
return urljoin(base_url, reverse('a2-idp-saml-metadata')) |
|
245 | ||
246 |
def pre_save(self, sender, instance, raw, using, update_fields, **kwargs): |
|
247 |
# we skip new instances |
|
248 |
if not instance.pk: |
|
249 |
return |
|
250 |
if not isinstance(instance, (self.User, self.Role, RoleAttribute, AttributeValue)): |
|
251 |
return |
|
252 |
# ignore last_login update on login |
|
253 |
if isinstance(instance, self.User) and update_fields == ['last_login']: |
|
254 |
return |
|
255 |
if isinstance(instance, RoleAttribute): |
|
256 |
instance = instance.role |
|
257 |
elif isinstance(instance, AttributeValue): |
|
258 |
if not isinstance(instance.owner, self.User): |
|
259 |
return |
|
260 |
instance = instance.owner |
|
261 |
self.saved(instance) |
|
262 | ||
263 |
def post_save(self, sender, instance, created, raw, using, update_fields, **kwargs): |
|
264 |
# during post_save we only handle new instances |
|
265 |
if isinstance(instance, self.RoleParenting): |
|
266 |
self.saved(*list(instance.child.all_members())) |
|
267 |
return |
|
268 |
if not created: |
|
269 |
return |
|
270 |
if not isinstance(instance, (self.User, self.Role, RoleAttribute, AttributeValue)): |
|
271 |
return |
|
272 |
if isinstance(instance, RoleAttribute): |
|
273 |
instance = instance.role |
|
274 |
elif isinstance(instance, AttributeValue): |
|
275 |
if not isinstance(instance.owner, self.User): |
|
276 |
return |
|
277 |
instance = instance.owner |
|
278 |
self.saved(instance) |
|
279 | ||
280 |
def pre_delete(self, sender, instance, using, **kwargs): |
|
281 |
if isinstance(instance, (self.User, self.Role)): |
|
282 |
self.deleted(copy.copy(instance)) |
|
283 |
elif isinstance(instance, RoleAttribute): |
|
284 |
instance = instance.role |
|
285 |
self.saved(instance) |
|
286 |
elif isinstance(instance, AttributeValue): |
|
287 |
if not isinstance(instance.owner, self.User): |
|
288 |
return |
|
289 |
instance = instance.owner |
|
290 |
self.saved(instance) |
|
291 |
elif isinstance(instance, self.RoleParenting): |
|
292 |
self.saved(*list(instance.child.all_members())) |
|
293 | ||
294 |
def m2m_changed(self, sender, instance, action, reverse, model, pk_set, using, **kwargs): |
|
295 |
if not action.startswith('post'): |
|
296 |
return |
|
297 |
if action.endswith('_clear'): |
|
298 |
return |
|
299 |
if reverse: |
|
300 |
self.saved(instance) |
|
301 |
else: |
|
302 |
for user in model.objects.filter(pk__in=pk_set): |
|
303 |
self.saved(user) |
hobo/multitenant/apps.py | ||
---|---|---|
19 | 19 |
super(TenantAwareThread, self).run() |
20 | 20 |
finally: |
21 | 21 |
connection.set_tenant(old_tenant) |
22 |
connection.close() |
|
22 | 23 | |
23 | 24 | |
24 | 25 |
class _Timer(TenantAwareThread): |
hobo/multitenant/models.py | ||
---|---|---|
65 | 65 |
"the public schema. Current schema is %s." |
66 | 66 |
% connection.schema_name) |
67 | 67 | |
68 |
os.rename(self.get_directory(), self.get_directory()+'.invalid')
|
|
68 |
os.rename(self.get_directory(), self.get_directory() + '.invalid')
|
|
69 | 69 | |
70 |
if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop) and not django_is_in_test_mode():
|
|
70 |
if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop): |
|
71 | 71 |
cursor = connection.cursor() |
72 | 72 |
cursor.execute('DROP SCHEMA %s CASCADE' % self.schema_name) |
tests_authentic/conftest.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
import pytest |
7 | 7 | |
8 | ||
8 | 9 |
@pytest.fixture |
9 | 10 |
def tenant_base(request, settings): |
10 | 11 |
base = tempfile.mkdtemp('combo-tenant-base') |
11 | 12 |
settings.TENANT_BASE = base |
13 | ||
12 | 14 |
def fin(): |
13 | 15 |
shutil.rmtree(base) |
14 | 16 |
request.addfinalizer(fin) |
15 | 17 |
return base |
16 | 18 | |
19 | ||
17 | 20 |
@pytest.fixture(scope='function') |
18 |
def tenant(db, request, settings, tenant_base): |
|
21 |
def tenant(transactional_db, request, settings, tenant_base):
|
|
19 | 22 |
from hobo.multitenant.models import Tenant |
20 | 23 |
base = tenant_base |
24 | ||
21 | 25 |
@pytest.mark.django_db |
22 | 26 |
def make_tenant(name): |
23 | 27 |
tenant_dir = os.path.join(base, name) |
... | ... | |
54 | 58 |
t.create_schema() |
55 | 59 |
return t |
56 | 60 |
tenants = [make_tenant('authentic.example.net')] |
61 | ||
57 | 62 |
def fin(): |
58 | 63 |
from django.db import connection |
59 | 64 |
connection.set_schema_to_public() |
tests_authentic/test_provisionning.py | ||
---|---|---|
1 | 1 |
# -*- coding: utf-8 -*- |
2 |
import json |
|
3 | ||
2 | 4 |
import pytest |
5 |
import lasso |
|
3 | 6 | |
4 | 7 |
from mock import patch, call, ANY |
5 | 8 | |
... | ... | |
7 | 10 | |
8 | 11 |
from tenant_schemas.utils import tenant_context |
9 | 12 | |
10 |
from authentic2.a2_rbac.models import Role |
|
13 |
from authentic2.saml.models import LibertyProvider |
|
14 |
from authentic2.a2_rbac.models import Role, RoleAttribute |
|
11 | 15 |
from authentic2.a2_rbac.utils import get_default_ou |
12 | 16 |
from authentic2.models import Attribute, AttributeValue |
17 |
from hobo.agent.authentic2.provisionning import provisionning |
|
13 | 18 | |
14 | 19 |
pytestmark = pytest.mark.django_db |
15 | 20 | |
16 | 21 | |
17 |
def test_provision_role(tenant):
|
|
18 |
with patch('hobo.agent.authentic2.apps.notify_agents') as notify_agents:
|
|
22 |
def test_provision_role(transactional_db, tenant, caplog):
|
|
23 |
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
|
|
19 | 24 |
with tenant_context(tenant): |
20 |
role = Role.objects.create(name='coin') |
|
25 |
LibertyProvider.objects.create(ou=get_default_ou(), name='provider', |
|
26 |
entity_id='http://provider.com', |
|
27 |
protocol_conformance=lasso.PROTOCOL_SAML_2_0) |
|
28 |
with provisionning: |
|
29 |
role = Role.objects.create(name='coin', ou=get_default_ou()) |
|
30 | ||
21 | 31 |
assert notify_agents.call_count == 1 |
22 | 32 |
arg = notify_agents.call_args |
23 | 33 |
assert arg == call(ANY) |
... | ... | |
25 | 35 |
assert isinstance(arg, dict) |
26 | 36 |
assert set(arg.keys()) == set([ |
27 | 37 |
'audience', '@type', 'objects', 'full']) |
28 |
assert arg['audience'] == [] |
|
38 |
assert arg['audience'] == ['http://provider.com']
|
|
29 | 39 |
assert arg['@type'] == 'provision' |
30 |
assert arg['full'] is True
|
|
40 |
assert arg['full'] is False
|
|
31 | 41 |
objects = arg['objects'] |
32 | 42 |
assert isinstance(objects, dict) |
33 | 43 |
assert set(objects.keys()) == set(['data', '@type']) |
34 | 44 |
assert objects['@type'] == 'role' |
35 | 45 |
data = objects['data'] |
36 | 46 |
assert isinstance(data, list) |
37 |
assert len(data) == 2 |
|
38 |
like_role = 0 |
|
39 |
for o in data: |
|
40 |
assert set(o.keys()) == set(['details', 'emails_to_members', |
|
41 |
'description', 'uuid', 'name', |
|
42 |
'slug', 'emails']) |
|
43 |
assert o['details'] == '' |
|
44 |
assert o['emails_to_members'] is False |
|
45 |
assert o['emails'] == [] |
|
46 |
if o['uuid'] == role.uuid and o['name'] == role.name \ |
|
47 |
and o['description'] == role.description \ |
|
48 |
and o['slug'] == role.slug: |
|
49 |
like_role += 1 |
|
50 |
assert like_role == 1 |
|
47 |
assert len(data) == 1 |
|
48 |
o = data[0] |
|
49 |
assert set(o.keys()) == set(['details', 'emails_to_members', |
|
50 |
'description', 'uuid', 'name', |
|
51 |
'slug', 'emails']) |
|
52 |
assert o['details'] == '' |
|
53 |
assert o['emails_to_members'] is False |
|
54 |
assert o['emails'] == [] |
|
51 | 55 | |
56 |
notify_agents.reset_mock() |
|
57 |
emails = ['john.doe@example.com', 'toto@entrouvert.com'] |
|
58 |
with provisionning: |
|
59 |
RoleAttribute.objects.create( |
|
60 |
role=role, name='emails', kind='json', |
|
61 |
value=json.dumps(emails)) |
|
52 | 62 | |
53 |
def test_provision_user(tenant): |
|
54 |
import lasso |
|
55 |
from authentic2.saml.models import LibertyProvider |
|
63 |
assert notify_agents.call_count == 1 |
|
64 |
arg = notify_agents.call_args |
|
65 |
assert arg == call(ANY) |
|
66 |
arg = arg[0][0] |
|
67 |
assert isinstance(arg, dict) |
|
68 |
assert set(arg.keys()) == set([ |
|
69 |
'audience', '@type', 'objects', 'full']) |
|
70 |
assert arg['audience'] == ['http://provider.com'] |
|
71 |
assert arg['@type'] == 'provision' |
|
72 |
assert arg['full'] is False |
|
73 |
objects = arg['objects'] |
|
74 |
assert isinstance(objects, dict) |
|
75 |
assert set(objects.keys()) == set(['data', '@type']) |
|
76 |
assert objects['@type'] == 'role' |
|
77 |
data = objects['data'] |
|
78 |
assert isinstance(data, list) |
|
79 |
assert len(data) == 1 |
|
80 |
o = data[0] |
|
81 |
assert set(o.keys()) == set(['details', 'emails_to_members', |
|
82 |
'description', 'uuid', 'name', |
|
83 |
'slug', 'emails']) |
|
84 |
assert o['details'] == '' |
|
85 |
assert o['emails_to_members'] is False |
|
86 |
assert o['emails'] == emails |
|
87 | ||
88 |
notify_agents.reset_mock() |
|
89 |
with provisionning: |
|
90 |
role.delete() |
|
56 | 91 | |
57 |
with patch('hobo.agent.authentic2.apps.notify_agents') as notify_agents: |
|
92 |
assert notify_agents.call_count == 1 |
|
93 |
arg = notify_agents.call_args |
|
94 |
assert arg == call(ANY) |
|
95 |
arg = arg[0][0] |
|
96 |
assert isinstance(arg, dict) |
|
97 |
assert set(arg.keys()) == set([ |
|
98 |
'audience', '@type', 'objects', 'full']) |
|
99 |
assert arg['audience'] == ['http://provider.com'] |
|
100 |
assert arg['@type'] == 'deprovision' |
|
101 |
assert arg['full'] is False |
|
102 |
objects = arg['objects'] |
|
103 |
assert isinstance(objects, dict) |
|
104 |
assert set(objects.keys()) == set(['data', '@type']) |
|
105 |
assert objects['@type'] == 'role' |
|
106 |
data = objects['data'] |
|
107 |
assert isinstance(data, list) |
|
108 |
assert len(data) == 1 |
|
109 |
o = data[0] |
|
110 |
assert set(o.keys()) == set(['uuid']) |
|
111 | ||
112 | ||
113 |
def test_provision_user(transactional_db, tenant, caplog): |
|
114 |
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents: |
|
58 | 115 |
with tenant_context(tenant): |
59 | 116 |
service = LibertyProvider.objects.create(ou=get_default_ou(), name='provider', |
60 | 117 |
entity_id='http://provider.com', |
61 | 118 |
protocol_conformance=lasso.PROTOCOL_SAML_2_0) |
62 | 119 |
role = Role.objects.create(name='coin', service=service, ou=get_default_ou()) |
63 | 120 |
role.attributes.create(kind='string', name='is_superuser', value='true') |
121 |
child_role = Role.objects.create(name='child', ou=get_default_ou()) |
|
64 | 122 |
notify_agents.reset_mock() |
65 | 123 |
User = get_user_model() |
66 | 124 |
attribute = Attribute.objects.create(label='Code postal', name='code_postal', |
67 | 125 |
kind='string') |
68 |
user = User.objects.create(username=u'Étienne', |
|
69 |
email='etienne.dugenou@example.net', |
|
70 |
first_name=u'Étienne', |
|
71 |
last_name=u'Dugenou', |
|
72 |
ou=get_default_ou()) |
|
126 |
with provisionning: |
|
127 |
user = User.objects.create(username=u'Étienne', |
|
128 |
email='etienne.dugenou@example.net', |
|
129 |
first_name=u'Étienne', |
|
130 |
last_name=u'Dugenou', |
|
131 |
ou=get_default_ou()) |
|
73 | 132 |
assert notify_agents.call_count == 1 |
74 | 133 |
arg = notify_agents.call_args |
75 | 134 |
assert arg == call(ANY) |
... | ... | |
103 | 162 |
notify_agents.reset_mock() |
104 | 163 |
attribute.set_value(user, '13400') |
105 | 164 |
user.is_superuser = True |
106 |
user.save() |
|
165 |
with provisionning: |
|
166 |
user.save() |
|
107 | 167 | |
108 |
assert notify_agents.call_count == 2
|
|
168 |
assert notify_agents.call_count == 1
|
|
109 | 169 |
arg = notify_agents.call_args |
110 | 170 |
assert arg == call(ANY) |
111 | 171 |
arg = arg[0][0] |
... | ... | |
137 | 197 |
assert o['is_superuser'] is True |
138 | 198 | |
139 | 199 |
notify_agents.reset_mock() |
140 |
AttributeValue.objects.get().delete() |
|
200 |
with provisionning: |
|
201 |
AttributeValue.objects.get().delete() |
|
141 | 202 | |
142 | 203 |
assert notify_agents.call_count == 1 |
143 | 204 |
arg = notify_agents.call_args |
... | ... | |
171 | 232 | |
172 | 233 |
user.is_superuser = False |
173 | 234 |
user.save() |
235 | ||
174 | 236 |
notify_agents.reset_mock() |
175 |
role.members.add(user) |
|
237 |
with provisionning: |
|
238 |
role.members.add(user) |
|
176 | 239 | |
177 | 240 |
assert notify_agents.call_count == 1 |
178 | 241 |
arg = notify_agents.call_args |
... | ... | |
209 | 272 |
assert o['is_superuser'] is True |
210 | 273 | |
211 | 274 |
notify_agents.reset_mock() |
212 |
user.roles.remove(role) |
|
275 |
with provisionning: |
|
276 |
user.roles.remove(role) |
|
213 | 277 | |
214 | 278 |
assert notify_agents.call_count == 1 |
215 | 279 |
arg = notify_agents.call_args |
... | ... | |
240 | 304 |
assert o['email'] == user.email |
241 | 305 |
assert o['roles'] == [] |
242 | 306 |
assert o['is_superuser'] is False |
307 | ||
308 |
notify_agents.reset_mock() |
|
309 |
with provisionning: |
|
310 |
user.roles.add(child_role) |
|
311 |
child_role.add_parent(role) |
|
312 | ||
313 |
assert notify_agents.call_count == 1 |
|
314 |
arg = notify_agents.call_args |
|
315 |
assert arg == call(ANY) |
|
316 |
arg = arg[0][0] |
|
317 |
assert isinstance(arg, dict) |
|
318 |
assert set(arg.keys()) == set([ |
|
319 |
'issuer', 'audience', '@type', 'objects', 'full']) |
|
320 |
assert arg['issuer'] == \ |
|
321 |
'http://%s/idp/saml2/metadata' % tenant.domain_url |
|
322 |
assert arg['audience'] == ['http://provider.com'] |
|
323 |
assert arg['@type'] == 'provision' |
|
324 |
assert arg['full'] is False |
|
325 |
objects = arg['objects'] |
|
326 |
assert isinstance(objects, dict) |
|
327 |
assert set(objects.keys()) == set(['data', '@type']) |
|
328 |
assert objects['@type'] == 'user' |
|
329 |
data = objects['data'] |
|
330 |
assert isinstance(data, list) |
|
331 |
assert len(data) == 1 |
|
332 |
for o in data: |
|
333 |
assert set(o.keys()) == set(['uuid', 'username', 'first_name', |
|
334 |
'is_superuser', 'last_name', 'email', 'roles']) |
|
335 |
assert o['uuid'] == user.uuid |
|
336 |
assert o['username'] == user.username |
|
337 |
assert o['first_name'] == user.first_name |
|
338 |
assert o['last_name'] == user.last_name |
|
339 |
assert o['email'] == user.email |
|
340 |
assert len(o['roles']) == 2 |
|
341 |
for r in o['roles']: |
|
342 |
r1 = { |
|
343 |
'uuid': role.uuid, |
|
344 |
'name': role.name, |
|
345 |
'slug': role.slug |
|
346 |
} |
|
347 |
r2 = { |
|
348 |
'uuid': child_role.uuid, |
|
349 |
'name': child_role.name, |
|
350 |
'slug': child_role.slug |
|
351 |
} |
|
352 |
assert r == r1 or r == r2 |
|
353 |
assert len(set(r['uuid'] for r in o['roles'])) == 2 |
|
354 |
assert o['is_superuser'] is True |
|
355 | ||
356 |
notify_agents.reset_mock() |
|
357 |
with provisionning: |
|
358 |
child_role.remove_parent(role) |
|
359 | ||
360 |
assert notify_agents.call_count == 1 |
|
361 |
arg = notify_agents.call_args |
|
362 |
assert arg == call(ANY) |
|
363 |
arg = arg[0][0] |
|
364 |
assert isinstance(arg, dict) |
|
365 |
assert set(arg.keys()) == set([ |
|
366 |
'issuer', 'audience', '@type', 'objects', 'full']) |
|
367 |
assert arg['issuer'] == \ |
|
368 |
'http://%s/idp/saml2/metadata' % tenant.domain_url |
|
369 |
assert arg['audience'] == ['http://provider.com'] |
|
370 |
assert arg['@type'] == 'provision' |
|
371 |
assert arg['full'] is False |
|
372 |
objects = arg['objects'] |
|
373 |
assert isinstance(objects, dict) |
|
374 |
assert set(objects.keys()) == set(['data', '@type']) |
|
375 |
assert objects['@type'] == 'user' |
|
376 |
data = objects['data'] |
|
377 |
assert isinstance(data, list) |
|
378 |
assert len(data) == 1 |
|
379 |
for o in data: |
|
380 |
assert set(o.keys()) == set(['uuid', 'username', 'first_name', |
|
381 |
'is_superuser', 'last_name', 'email', 'roles']) |
|
382 |
assert o['uuid'] == user.uuid |
|
383 |
assert o['username'] == user.username |
|
384 |
assert o['first_name'] == user.first_name |
|
385 |
assert o['last_name'] == user.last_name |
|
386 |
assert o['email'] == user.email |
|
387 |
assert o['roles'] == [{ |
|
388 |
'uuid': child_role.uuid, |
|
389 |
'name': child_role.name, |
|
390 |
'slug': child_role.slug |
|
391 |
}] |
|
392 |
assert o['is_superuser'] is False |
|
393 | ||
243 | 394 |
notify_agents.reset_mock() |
244 |
user.delete() |
|
395 |
with provisionning: |
|
396 |
user.delete() |
|
245 | 397 |
assert notify_agents.call_count == 1 |
246 | 398 |
arg = notify_agents.call_args |
247 | 399 |
assert arg == call(ANY) |
tox.ini | ||
---|---|---|
31 | 31 |
pytest-cov |
32 | 32 |
pytest-django |
33 | 33 |
pytest-mock |
34 |
pytest-capturelog |
|
34 | 35 |
coverage |
35 | 36 |
raven |
36 | 37 |
cssselect |
37 | 38 |
WebTest |
38 | 39 |
django-mellon |
39 |
multitenant: pytest-capturelog |
|
40 | 40 |
multitenant,passerelle: celery |
41 | 41 |
authentic: http://git.entrouvert.org/authentic.git/snapshot/authentic-master.tar.gz |
42 | 42 |
passerelle: http://git.entrouvert.org/passerelle.git/snapshot/passerelle-master.tar.gz |
43 |
- |