0006-derni-res-remarques-to-rebase.patch
src/authentic2/manager/templates/authentic2/manager/user_import.html | ||
---|---|---|
11 | 11 |
{% block breadcrumb %} |
12 | 12 |
{{ block.super }} |
13 | 13 |
<a href="{% url 'a2-manager-users' %}">{% trans 'Users' %}</a> |
14 |
<a href="{% url 'a2-manager-users-imports' %}">{% trans 'Import' %}</a> |
|
14 |
<a href="{% url 'a2-manager-users-imports' %}">{% trans 'Imports' %}</a>
|
|
15 | 15 |
<a href="{% url 'a2-manager-users-import' uuid=user_import.uuid %}">{% trans "User Import" %} {{ user_import.created }}</a> |
16 | 16 |
{% endblock %} |
17 | 17 | |
... | ... | |
19 | 19 |
<aside id="sidebar"> |
20 | 20 |
<div> |
21 | 21 |
<h3>{% trans "Modify import" %}</h3> |
22 |
<form method="post"> |
|
22 |
<form method="post" id="action-form">
|
|
23 | 23 |
{% csrf_token %} |
24 | 24 |
{{ form|with_template }} |
25 | 25 |
<div class="buttons"> |
26 |
<button name="create">{% trans "Modify" %}</button>
|
|
26 |
<button name="modify">{% trans "Modify" %}</button>
|
|
27 | 27 |
<button name="simulate">{% trans "Simulate" %}</button> |
28 | 28 |
<button name="execute">{% trans "Execute" %}</button> |
29 | 29 |
</div> |
... | ... | |
31 | 31 |
</div> |
32 | 32 |
<div> |
33 | 33 |
<h3>{% trans "Download" %}</h3> |
34 |
<form action="download/{{ user_import.filename }}"> |
|
34 |
<form action="download/{{ user_import.filename }}" id="download-form">
|
|
35 | 35 |
<div class="buttons"> |
36 | 36 |
<button>{% trans "Download" %}</button> |
37 | 37 |
</div> |
... | ... | |
55 | 55 |
</thead> |
56 | 56 |
<tbody> |
57 | 57 |
{% for report in reports %} |
58 |
<tr> |
|
59 |
<td>{% if report.state != 'running' %}<a href="{% url "a2-manager-users-import-report" import_uuid=user_import.uuid report_uuid=report.uuid %}">{{ report.created }}</a>{% else %}{{ report.created }}{% endif %}</td> |
|
60 |
<td>{{ report.state }} {% if report.state == 'error' %}"{{ report.exception }}"{% endif %}</td>
|
|
61 |
<td>{% if not report.simulate %}<span class="icon-check"></span>{% endif %}</td> |
|
62 |
<td>{% if report.simulate %}<form method="post">{% csrf_token %}<button name="delete" value="{{ report.uuid }}">{% trans "Delete" %}</button></form>{% endif %}</td>
|
|
58 |
<tr data-uuid="{{ report.uuid }}">
|
|
59 |
<td class="created">{% if report.state != 'running' %}<a href="{% url "a2-manager-users-import-report" import_uuid=user_import.uuid report_uuid=report.uuid %}">{{ report.created }}</a>{% else %}{{ report.created }}{% endif %}</td>
|
|
60 |
<td class="state"><span>{{ report.state }}</span> {% if report.state == 'error' %}"{{ report.exception }}"{% endif %}</td>
|
|
61 |
<td class="applied">{% if not report.simulate %}<span class="icon-check"></span>{% endif %}</td>
|
|
62 |
<td class="delete-action">{% if report.simulate %}<form method="post" id="delete-form-{{ report.uuid }}">{% csrf_token %}<button name="delete" value="{{ report.uuid }}">{% trans "Delete" %}</button></form>{% endif %}</td>
|
|
63 | 63 |
</tr> |
64 | 64 |
{% endfor %} |
65 | 65 |
</tbody> |
src/authentic2/manager/templates/authentic2/manager/user_import_report.html | ||
---|---|---|
11 | 11 |
{% block breadcrumb %} |
12 | 12 |
{{ block.super }} |
13 | 13 |
<a href="{% url 'a2-manager-users' %}">{% trans 'Users' %}</a> |
14 |
<a href="{% url 'a2-manager-users-imports' %}">{% trans 'Import' %}</a> |
|
14 |
<a href="{% url 'a2-manager-users-imports' %}">{% trans 'Imports' %}</a>
|
|
15 | 15 |
<a href="{% url 'a2-manager-users-import' uuid=user_import.uuid %}">{% trans "User Import" %} {{ user_import.created }}</a> |
16 | 16 |
<a href="{% url 'a2-manager-users-import-report' import_uuid=user_import.uuid report_uuid=report.uuid%}">{{ report_title }} {{ report.created }}</a> |
17 | 17 |
{% endblock %} |
src/authentic2/manager/templates/authentic2/manager/users.html | ||
---|---|---|
21 | 21 |
{% trans "Add user" %} |
22 | 22 |
</a> |
23 | 23 |
{% endif %} |
24 |
{% if extra_actions %} |
|
24 | 25 |
<ul class="extra-actions-menu"> |
25 |
<li><a href="{% url "a2-manager-users-imports" %}">{% trans 'Import Users' %}</a></li> |
|
26 |
{% for extra_action in extra_actions %} |
|
27 |
<li><a href="{{ extra_action.url }}">{{ extra_action.label }}</a></li> |
|
28 |
{% endfor %} |
|
26 | 29 |
</ul> |
30 |
{% endif %} |
|
27 | 31 |
</span> |
28 | 32 | |
29 | 33 |
{% endblock %} |
src/authentic2/manager/user_import.py | ||
---|---|---|
186 | 186 |
data['exception'] = exception |
187 | 187 |
data['importer'] = importer |
188 | 188 |
t = threading.Thread(target=target) |
189 |
t.daemon = True |
|
189 | 190 |
with self.data_update as data: |
190 | 191 |
data['state'] = 'running' |
191 | 192 |
if start: |
src/authentic2/manager/user_views.py | ||
---|---|---|
42 | 42 |
from .views import (BaseTableView, BaseAddView, BaseEditView, ActionMixin, |
43 | 43 |
OtherActionsMixin, Action, ExportMixin, BaseSubTableView, |
44 | 44 |
HideOUColumnMixin, BaseDeleteView, BaseDetailView, |
45 |
PermissionMixin) |
|
45 |
PermissionMixin, MediaMixin)
|
|
46 | 46 |
from .tables import UserTable, UserRolesTable, OuUserRolesTable |
47 | 47 |
from .forms import (UserSearchForm, UserAddForm, UserEditForm, |
48 | 48 |
UserChangePasswordForm, ChooseUserRoleForm, |
... | ... | |
110 | 110 |
ou = self.search_form.cleaned_data.get('ou') |
111 | 111 |
if ou and self.request.user.has_ou_perm('custom_user.add_user', ou): |
112 | 112 |
ctx['add_ou'] = ou |
113 |
extra_actions = ctx['extra_actions'] = [] |
|
114 |
if self.request.user.has_perm('custom_user.admin_user'): |
|
115 |
extra_actions.append({ |
|
116 |
'url': reverse('a2-manager-users-imports'), |
|
117 |
'label': _('Import users'), |
|
118 |
}) |
|
113 | 119 |
return ctx |
114 | 120 | |
115 | 121 | |
... | ... | |
626 | 632 |
user_delete = UserDeleteView.as_view() |
627 | 633 | |
628 | 634 | |
629 |
class UserImportsView(PermissionMixin, FormView): |
|
635 |
class UserImportsView(MediaMixin, PermissionMixin, FormView):
|
|
630 | 636 |
form_class = UserNewImportForm |
637 |
permissions = ['custom_user.admin_user'] |
|
638 |
permissions_global = True |
|
631 | 639 |
template_name = 'authentic2/manager/user_imports.html' |
632 | 640 | |
633 | 641 |
def post(self, request, *args, **kwargs): |
... | ... | |
656 | 664 |
user_imports = UserImportsView.as_view() |
657 | 665 | |
658 | 666 | |
659 |
class UserImportView(PermissionMixin, FormView): |
|
667 |
class UserImportView(MediaMixin, PermissionMixin, FormView):
|
|
660 | 668 |
form_class = UserEditImportForm |
661 | 669 |
permissions = ['custom_user.admin_user'] |
670 |
permissions_global = True |
|
662 | 671 |
template_name = 'authentic2/manager/user_import.html' |
663 | 672 | |
664 | 673 |
def dispatch(self, request, uuid, **kwargs): |
... | ... | |
715 | 724 |
user_import = UserImportView.as_view() |
716 | 725 | |
717 | 726 | |
718 |
class UserImportReportView(PermissionMixin, TemplateView): |
|
727 |
class UserImportReportView(MediaMixin, PermissionMixin, TemplateView):
|
|
719 | 728 |
form_class = UserEditImportForm |
720 | 729 |
permissions = ['custom_user.admin_user'] |
730 |
permissions_global = True |
|
721 | 731 |
template_name = 'authentic2/manager/user_import_report.html' |
722 | 732 | |
723 | 733 |
def dispatch(self, request, import_uuid, report_uuid): |
src/authentic2/manager/views.py | ||
---|---|---|
101 | 101 |
class PermissionMixin(object): |
102 | 102 |
'''Control access to views based on permissions''' |
103 | 103 |
permissions = None |
104 |
permissions_global = False |
|
104 | 105 | |
105 | 106 |
def authorize(self, request, *args, **kwargs): |
106 | 107 |
if hasattr(self, 'model'): |
... | ... | |
130 | 131 |
and not request.user.has_perm_any(self.permissions): |
131 | 132 |
raise PermissionDenied |
132 | 133 |
else: |
133 |
if self.permissions \ |
|
134 |
and not request.user.has_perm_any(self.permissions): |
|
135 |
raise PermissionDenied |
|
134 |
if self.permissions: |
|
135 |
if self.permissions_global and not request.user.has_perms(self.permissions): |
|
136 |
raise PermissionDenied |
|
137 |
if not self.permissions_global and not request.user.has_perm_any(self.permissions): |
|
138 |
raise PermissionDenied |
|
136 | 139 | |
137 | 140 |
def dispatch(self, request, *args, **kwargs): |
138 | 141 |
response = self.authorize(request, *args, **kwargs) |
tests/test_user_manager.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
1 | 2 |
# authentic2 - versatile identity manager |
2 | 3 |
# Copyright (C) 2010-2019 Entr'ouvert |
3 | 4 |
# |
... | ... | |
14 | 15 |
# You should have received a copy of the GNU Affero General Public License |
15 | 16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 17 |
import csv |
18 |
import time |
|
19 | ||
20 |
from webtest import Upload |
|
17 | 21 | |
18 | 22 |
from django.core.urlresolvers import reverse |
19 | 23 | |
... | ... | |
25 | 29 |
from authentic2.custom_user.models import User |
26 | 30 |
from authentic2.models import Attribute, AttributeValue |
27 | 31 |
from authentic2.a2_rbac.utils import get_default_ou |
32 |
from authentic2.manager import user_import |
|
28 | 33 | |
29 | 34 | |
30 | 35 |
from utils import login, get_link_from_mail, skipif_sqlite |
... | ... | |
210 | 215 | |
211 | 216 |
response = app.get('/manage/users/?search-ou=%s' % ou1.id) |
212 | 217 |
assert response.pyquery('td.username') |
218 | ||
219 | ||
220 |
def test_user_import(transactional_db, app, admin, ou1, admin_ou1, media): |
|
221 |
Attribute.objects.create(name='phone', kind='phone_number', label='Numéro de téléphone') |
|
222 | ||
223 |
user_count = User.objects.count() |
|
224 | ||
225 |
assert Attribute.objects.count() == 3 |
|
226 | ||
227 |
response = login(app, admin, '/manage/users/') |
|
228 | ||
229 |
response = response.click('Import users') |
|
230 |
response.form.set('import_file', |
|
231 |
Upload( |
|
232 |
'users.csv', |
|
233 |
u'''email key verified,first_name,last_name,phone |
|
234 |
tnoel@entrouvert.com,Thomas,Noël,1234 |
|
235 |
fpeters@entrouvert.com,Frédéric,Péters,5678 |
|
236 |
x,x,x,x'''.encode('utf-8'), |
|
237 |
'application/octet-stream')) |
|
238 |
response.form.set('encoding', 'utf-8') |
|
239 |
response.form.set('ou', str(get_default_ou().pk)) |
|
240 |
response = response.form.submit() |
|
241 | ||
242 |
imports = list(user_import.UserImport.all()) |
|
243 |
assert len(imports) == 1 |
|
244 |
_import_uuid = response.location.split('/')[-2] |
|
245 |
_import = user_import.UserImport(uuid=_import_uuid) |
|
246 |
assert _import.exists() |
|
247 | ||
248 |
response = response.follow() |
|
249 |
response = response.forms['action-form'].submit(name='simulate') |
|
250 | ||
251 |
reports = list(_import.reports) |
|
252 |
assert len(reports) == 1 |
|
253 |
uuid = reports[0].uuid |
|
254 | ||
255 |
response = response.follow() |
|
256 | ||
257 |
def assert_timeout(duration, wait_function): |
|
258 |
start = time.time() |
|
259 |
while True: |
|
260 |
result = wait_function() |
|
261 |
if result is not None: |
|
262 |
return result |
|
263 |
assert time.time() - start < duration, '%s timed out after %s seconds' % (wait_function, duration) |
|
264 |
time.sleep(0.001) |
|
265 | ||
266 |
def wait_finished(): |
|
267 |
new_resp = response.click('User Import') |
|
268 |
if new_resp.pyquery('tr[data-uuid="%s"] td.state span' % uuid).text() == 'finished': |
|
269 |
return new_resp |
|
270 | ||
271 |
simulate = reports[0] |
|
272 |
assert simulate.simulate |
|
273 | ||
274 |
response = assert_timeout(20, wait_finished) |
|
275 | ||
276 |
response = response.click(href=simulate.uuid) |
|
277 | ||
278 |
assert len(response.pyquery('table.main tbody tr')) == 3 |
|
279 |
assert len(response.pyquery('table.main tbody tr.row-valid')) == 2 |
|
280 |
assert len(response.pyquery('table.main tbody tr.row-invalid')) == 1 |
|
281 | ||
282 |
assert User.objects.count() == user_count |
|
283 | ||
284 |
response = response.click('User Import') |
|
285 |
response = response.forms['action-form'].submit(name='execute') |
|
286 | ||
287 |
execute = list(report for report in _import.reports if not report.simulate)[0] |
|
288 |
uuid = execute.uuid |
|
289 | ||
290 |
response = response.follow() |
|
291 |
response = assert_timeout(20, wait_finished) |
|
292 | ||
293 |
assert User.objects.count() == user_count + 2 |
|
294 |
assert User.objects.filter( |
|
295 |
email='tnoel@entrouvert.com', |
|
296 |
first_name=u'Thomas', |
|
297 |
last_name=u'Noël', |
|
298 |
attribute_values__content='1234').count() == 1 |
|
299 |
assert User.objects.filter( |
|
300 |
email='fpeters@entrouvert.com', |
|
301 |
first_name=u'Frédéric', |
|
302 |
last_name=u'Péters', |
|
303 |
attribute_values__content='5678').count() == 1 |
|
304 | ||
305 |
# logout |
|
306 |
app.session.flush() |
|
307 |
response = login(app, admin_ou1, '/manage/users/') |
|
308 | ||
309 |
app.get('/manage/users/import/', status=403) |
|
310 |
app.get('/manage/users/import/%s/' % _import.uuid, status=403) |
|
311 |
app.get('/manage/users/import/%s/%s/' % (_import.uuid, simulate.uuid), status=403) |
|
312 |
app.get('/manage/users/import/%s/%s/' % (_import.uuid, execute.uuid), status=403) |
|
213 |
- |