0002-data_transfer-use-ValidationError-instead-of-DataImp.patch
src/authentic2/data_transfer.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 |
from functools import wraps |
|
18 | ||
19 |
from django.core.exceptions import ValidationError |
|
17 | 20 |
from django.contrib.contenttypes.models import ContentType |
21 |
from django.utils.translation import ugettext_lazy as _ |
|
18 | 22 | |
19 | 23 |
from django_rbac.models import Operation |
20 | 24 |
from django_rbac.utils import ( |
21 | 25 |
get_ou_model, get_role_model, get_role_parenting_model, get_permission_model) |
26 | ||
22 | 27 |
from authentic2.a2_rbac.models import RoleAttribute |
28 |
from authentic2.utils.lazy import lazy_join |
|
23 | 29 | |
24 | 30 | |
25 | 31 |
def update_model(obj, d): |
... | ... | |
128 | 134 |
self.role_attributes_update = role_attributes_update |
129 | 135 | |
130 | 136 | |
131 |
class DataImportError(Exception): |
|
132 |
pass |
|
133 | ||
134 | ||
135 | 137 |
class RoleDeserializer(object): |
136 | 138 |
def __init__(self, d, import_context): |
137 | 139 |
self._import_context = import_context |
... | ... | |
151 | 153 |
else: |
152 | 154 |
self._role_d[key] = value |
153 | 155 | |
156 |
def wraps_validationerror(func): |
|
157 |
@wraps(func) |
|
158 |
def f(self, *args, **kwargs): |
|
159 |
try: |
|
160 |
return func(self, *args, **kwargs) |
|
161 |
except ValidationError as e: |
|
162 |
raise ValidationError(_('Role "%s": %s') % ( |
|
163 |
self._role_d.get('name', self._role_d.get('slug')), |
|
164 |
lazy_join(', ', [v.message for v in e.error_list]))) |
|
165 |
return f |
|
166 | ||
167 |
@wraps_validationerror |
|
154 | 168 |
def deserialize(self): |
155 | 169 |
ou_d = self._role_d['ou'] |
156 | 170 |
has_ou = bool(ou_d) |
157 | 171 |
ou = None if not has_ou else search_ou(ou_d) |
158 | 172 |
if has_ou and not ou: |
159 |
raise DataImportError( |
|
160 |
"Can't import role because missing Organizational Unit: %s" % ou_d) |
|
173 |
raise ValidationError(_("Can't import role because missing Organizational Unit: %s") % ou_d) |
|
161 | 174 | |
162 | 175 |
kwargs = self._role_d.copy() |
163 | 176 |
kwargs.pop('ou', None) |
... | ... | |
172 | 185 |
update_model(self._obj, kwargs) |
173 | 186 |
else: # Create role |
174 | 187 |
if 'uuid' in kwargs and not kwargs['uuid']: |
175 |
raise DataImportError("Cannot import role '%s' with empty uuid" |
|
176 |
% kwargs.get('name')) |
|
188 |
raise ValidationError(_("Cannot import role '%s' with empty uuid") % kwargs.get('name')) |
|
177 | 189 |
self._obj = get_role_model().objects.create(**kwargs) |
178 | 190 |
status = 'created' |
179 | 191 | |
... | ... | |
184 | 196 |
self._obj.get_admin_role() |
185 | 197 |
return self._obj, status |
186 | 198 | |
199 |
@wraps_validationerror |
|
187 | 200 |
def attributes(self): |
188 | 201 |
""" Update attributes (delete everything then create) |
189 | 202 |
""" |
... | ... | |
199 | 212 | |
200 | 213 |
return created, deleted |
201 | 214 | |
215 |
@wraps_validationerror |
|
202 | 216 |
def parentings(self): |
203 | 217 |
""" Update parentings (delete everything then create) |
204 | 218 |
""" |
... | ... | |
212 | 226 |
for parent_d in self._parents: |
213 | 227 |
parent = search_role(parent_d) |
214 | 228 |
if not parent: |
215 |
raise DataImportError("Could not find role: %s" % parent_d)
|
|
229 |
raise ValidationError(_("Could not find parent role: %s") % parent_d)
|
|
216 | 230 |
created.append(Parenting.objects.create( |
217 | 231 |
child=self._obj, direct=True, parent=parent)) |
218 | 232 | |
219 | 233 |
return created, deleted |
220 | 234 | |
235 |
@wraps_validationerror |
|
221 | 236 |
def permissions(self): |
222 | 237 |
""" Update permissions (delete everything then create) |
223 | 238 |
""" |
... | ... | |
300 | 315 |
result = ImportResult() |
301 | 316 | |
302 | 317 |
if not isinstance(json_d, dict): |
303 |
raise DataImportError('Export file is invalid: not a dictionnary')
|
|
318 |
raise ValidationError(_('Import file is invalid: not a dictionnary'))
|
|
304 | 319 | |
305 | 320 |
if import_context.import_ous: |
306 | 321 |
for ou_d in json_d.get('ous', []): |
307 | 322 |
result.update_ous(*import_ou(ou_d)) |
308 | 323 | |
309 | 324 |
if import_context.import_roles: |
310 |
roles_ds = [RoleDeserializer(role_d, import_context) for role_d in json_d.get('roles', []) |
|
311 |
if not role_d['slug'].startswith('_')] |
|
325 |
roles_ds = [] |
|
326 |
for role_d in json_d.get('roles', []): |
|
327 |
# ignore internal roles |
|
328 |
if role_d['slug'].startswith('_'): |
|
329 |
continue |
|
330 |
roles_ds.append(RoleDeserializer(role_d, import_context)) |
|
312 | 331 | |
313 | 332 |
for ds in roles_ds: |
314 | 333 |
result.update_roles(*ds.deserialize()) |
... | ... | |
326 | 345 |
result.update_permissions(*ds.permissions()) |
327 | 346 | |
328 | 347 |
if import_context.ou_delete_orphans: |
329 |
raise DataImportError( |
|
330 |
"Unsupported context value for ou_delete_orphans : %s" % ( |
|
331 |
import_context.ou_delete_orphans)) |
|
348 |
raise ValidationError(_("Unsupported context value for ou_delete_orphans : %s") % ( |
|
349 |
import_context.ou_delete_orphans)) |
|
332 | 350 | |
333 | 351 |
if import_context.role_delete_orphans: |
334 | 352 |
# FIXME : delete each role that is in DB but not in the export |
335 |
raise DataImportError( |
|
336 |
"Unsupported context value for role_delete_orphans : %s" % ( |
|
337 |
import_context.role_delete_orphans)) |
|
353 |
raise ValidationError(_("Unsupported context value for role_delete_orphans : %s") % ( |
|
354 |
import_context.role_delete_orphans)) |
|
338 | 355 | |
339 | 356 |
return result |
src/authentic2/manager/views.py | ||
---|---|---|
17 | 17 |
import json |
18 | 18 |
import inspect |
19 | 19 | |
20 |
from django.core.exceptions import PermissionDenied |
|
20 |
from django.core.exceptions import PermissionDenied, ValidationError
|
|
21 | 21 |
from django.db import transaction |
22 | 22 |
from django.views.generic.base import ContextMixin |
23 | 23 |
from django.views.generic import (FormView, UpdateView, CreateView, DeleteView, TemplateView, |
... | ... | |
40 | 40 | |
41 | 41 |
from django_rbac.utils import get_ou_model |
42 | 42 | |
43 |
from authentic2.data_transfer import export_site, import_site, DataImportError, ImportContext
|
|
43 |
from authentic2.data_transfer import export_site, import_site, ImportContext |
|
44 | 44 |
from authentic2.forms.profile import modelform_factory |
45 | 45 |
from authentic2.utils import redirect, batch_queryset |
46 | 46 |
from authentic2.decorators import json as json_view |
... | ... | |
715 | 715 | |
716 | 716 |
def form_valid(self, form): |
717 | 717 |
try: |
718 |
json_site = json.loads(force_text(
|
|
719 |
self.request.FILES['site_json'].read()))
|
|
718 |
json_site = json.loads( |
|
719 |
force_text(self.request.FILES['site_json'].read()))
|
|
720 | 720 |
except ValueError: |
721 | 721 |
form.add_error('site_json', _('File is not in the expected JSON format.')) |
722 | 722 |
return self.form_invalid(form) |
... | ... | |
724 | 724 |
try: |
725 | 725 |
with transaction.atomic(): |
726 | 726 |
import_site(json_site, ImportContext()) |
727 |
except DataImportError as e:
|
|
728 |
form.add_error('site_json', six.text_type(e))
|
|
727 |
except ValidationError as e:
|
|
728 |
form.add_error('site_json', e)
|
|
729 | 729 |
return self.form_invalid(form) |
730 | 730 | |
731 | 731 |
return super(SiteImportView, self).form_valid(form) |
tests/test_data_transfer.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 |
from django_rbac.utils import get_role_model, get_ou_model |
|
18 | 17 |
import pytest |
19 | 18 | |
19 |
from django.core.exceptions import ValidationError |
|
20 | ||
21 |
from django_rbac.utils import get_role_model, get_ou_model |
|
22 | ||
20 | 23 |
from authentic2.a2_rbac.models import RoleParenting |
21 | 24 |
from authentic2.data_transfer import ( |
22 | 25 |
export_site, |
23 | 26 |
ExportContext, |
24 |
DataImportError, |
|
25 | 27 |
export_roles, |
26 | 28 |
import_site, |
27 | 29 |
export_ous, |
... | ... | |
159 | 161 |
'uuid': get_hex_uuid(), 'name': 'some role', 'description': 'role description', |
160 | 162 |
'slug': 'some-role', 'ou': {'slug': 'some-ou'}, 'service': None}, |
161 | 163 |
ImportContext()) |
162 |
with pytest.raises(DataImportError):
|
|
164 |
with pytest.raises(ValidationError):
|
|
163 | 165 |
rd.deserialize() |
164 | 166 | |
165 | 167 | |
... | ... | |
255 | 257 |
'uuid': get_hex_uuid(), 'ou': None, 'service': None} |
256 | 258 |
rd = RoleDeserializer(child_role_dict, ImportContext()) |
257 | 259 |
rd.deserialize() |
258 |
with pytest.raises(DataImportError) as excinfo:
|
|
260 |
with pytest.raises(ValidationError) as excinfo:
|
|
259 | 261 |
rd.parentings() |
260 | 262 | |
261 |
assert "Could not find role" in str(excinfo.value) |
|
263 |
assert "Could not find parent role" in str(excinfo.value)
|
|
262 | 264 | |
263 | 265 | |
264 | 266 |
def test_role_deserializer_permissions(db): |
... | ... | |
473 | 475 |
def test_import_roles_role_delete_orphans(db): |
474 | 476 |
roles = [{ |
475 | 477 |
'name': 'some role', 'description': 'some role description', 'slug': '_some-role'}] |
476 |
with pytest.raises(DataImportError):
|
|
478 |
with pytest.raises(ValidationError):
|
|
477 | 479 |
import_site({'roles': roles}, ImportContext(role_delete_orphans=True)) |
478 | 480 | |
479 | 481 | |
... | ... | |
512 | 514 |
import_site(d, ImportContext(import_roles=False, import_ous=False)) |
513 | 515 |
assert Role.objects.exclude(slug__startswith='_').count() == 0 |
514 | 516 |
assert OU.objects.exclude(slug='default').count() == 0 |
515 |
with pytest.raises(DataImportError) as e:
|
|
517 |
with pytest.raises(ValidationError) as e:
|
|
516 | 518 |
import_site(d, ImportContext(import_roles=True, import_ous=False)) |
517 | 519 |
assert 'missing Organizational' in e.value.args[0] |
518 | 520 |
assert Role.objects.exclude(slug__startswith='_').count() == 0 |
tests/test_import_export_site_cmd.py | ||
---|---|---|
17 | 17 |
import random |
18 | 18 |
import json |
19 | 19 | |
20 |
from django.core.exceptions import ValidationError |
|
21 | ||
20 | 22 |
from django.utils import six |
21 | 23 |
from django.utils.six.moves import builtins as __builtin__ |
22 | 24 |
from django.core import management |
... | ... | |
122 | 124 | |
123 | 125 | |
124 | 126 |
def test_import_site_cmd_unhandled_context_option(db, monkeypatch, capsys, json_fixture): |
125 |
from authentic2.data_transfer import DataImportError |
|
126 | ||
127 | 127 |
content = { |
128 | 128 |
'roles': [ |
129 | 129 |
{ |
... | ... | |
138 | 138 | |
139 | 139 |
Role.objects.create(uuid='dqfewrvesvews2532', slug='role-slug', name='role-name') |
140 | 140 | |
141 |
with pytest.raises(DataImportError):
|
|
141 |
with pytest.raises(ValidationError):
|
|
142 | 142 |
management.call_command( |
143 | 143 |
'import_site', '-o', 'role-delete-orphans', json_fixture(content)) |
144 | 144 | |
... | ... | |
205 | 205 | |
206 | 206 | |
207 | 207 |
def test_import_site_empty_uuids(db, monkeypatch, json_fixture): |
208 |
from authentic2.data_transfer import DataImportError |
|
209 |
with pytest.raises(DataImportError): |
|
208 |
with pytest.raises(ValidationError): |
|
210 | 209 |
management.call_command('import_site', json_fixture({ |
211 | 210 |
'roles': [ |
212 | 211 |
{ |
213 |
- |