0001-csv_import-allow-settings-password-hash-50156.patch
src/authentic2/csv_import.py | ||
---|---|---|
23 | 23 |
import attr |
24 | 24 | |
25 | 25 |
from django import forms |
26 |
from django.core.exceptions import FieldDoesNotExist |
|
26 |
from django.contrib.auth.hashers import identify_hasher |
|
27 |
from django.core.exceptions import FieldDoesNotExist, ValidationError |
|
27 | 28 |
from django.core.validators import RegexValidator |
28 | 29 |
from django.db import IntegrityError, models |
29 | 30 |
from django.db.transaction import atomic |
... | ... | |
249 | 250 |
SOURCE_COLUMNS = set([SOURCE_NAME, SOURCE_ID]) |
250 | 251 |
ROLE_NAME = '_role_name' |
251 | 252 |
ROLE_SLUG = '_role_slug' |
253 |
PASSWORD_HASH = 'password_hash' |
|
252 | 254 |
REGISTRATION = '@registration' |
253 | 255 |
REGISTRATION_RESET_EMAIL = 'send-email' |
254 |
SPECIAL_COLUMNS = SOURCE_COLUMNS | {ROLE_NAME, ROLE_SLUG, REGISTRATION} |
|
256 |
SPECIAL_COLUMNS = SOURCE_COLUMNS | {ROLE_NAME, ROLE_SLUG, REGISTRATION, PASSWORD_HASH}
|
|
255 | 257 | |
256 | 258 | |
257 | 259 |
class ImportUserForm(BaseUserForm): |
... | ... | |
268 | 270 |
choices=choices, |
269 | 271 |
label=_('Registration option'), |
270 | 272 |
required=False) |
273 |
locals()[PASSWORD_HASH] = forms.CharField( |
|
274 |
label=_('Password hash'), |
|
275 |
required=False) |
|
276 | ||
271 | 277 | |
272 | 278 |
def clean(self): |
273 | 279 |
super(BaseUserForm, self).clean() |
274 | 280 |
self._validate_unique = False |
275 | 281 | |
282 |
def clean_password_hash(self): |
|
283 |
password_hash = self.cleaned_data['password_hash'] |
|
284 |
try: |
|
285 |
hasher = identify_hasher(password_hash) |
|
286 |
except ValueError: |
|
287 |
raise ValidationError(_('Invalid password format or unknown hashing algorithm.')) |
|
288 |
return password_hash |
|
289 | ||
276 | 290 | |
277 | 291 |
class ImportUserFormWithExternalId(ImportUserForm): |
278 | 292 |
locals()[SOURCE_NAME] = forms.CharField( |
... | ... | |
722 | 736 |
success &= self.add_role(cell, user, do_clear=True) |
723 | 737 |
elif cell.header.name == REGISTRATION and row.action == 'create': |
724 | 738 |
success &= self.registration_option(cell, user) |
739 |
elif cell.header.name == PASSWORD_HASH: |
|
740 |
user.password = cell.value |
|
741 |
user.save() |
|
725 | 742 | |
726 | 743 |
setattr(self, row.action + 'd', getattr(self, row.action + 'd') + 1) |
727 | 744 |
return success |
src/authentic2/manager/templates/authentic2/manager/user_imports.html | ||
---|---|---|
250 | 250 |
<pre>email key,first_name,last_name,@registration |
251 | 251 |
john.doe@example.com,John,Doe,send-email |
252 | 252 |
jane.doe@example.com,Jane,Doe, |
253 |
</pre> |
|
254 |
</blockquote> |
|
255 |
<p>{% blocktrans trimmed %}Importing email, first and last name of users |
|
256 |
and setting a password using hash in standard Django format.{% endblocktrans %}</p> |
|
257 |
<blockquote> |
|
258 |
<pre>email key,first_name,last_name,password_hash |
|
259 |
john.doe@example.com,John,Doe,pbkdf2_sha256$36000$oTHdVaoMjnCp$uTkpF7Ne6KV/L5gAerS7mngXM96DOEaLsLMZ251HJ/M= |
|
253 | 260 |
</pre> |
254 | 261 |
</blockquote> |
255 | 262 |
</div> |
src/authentic2/manager/user_views.py | ||
---|---|---|
777 | 777 |
'name': attribute.name, |
778 | 778 |
'key': attribute.name == key, |
779 | 779 |
}) |
780 |
help_columns.append({ |
|
781 |
'label': _('Password hash'), |
|
782 |
'name': 'password_hash', |
|
783 |
'key': False, |
|
784 |
}) |
|
780 | 785 |
ctx['help_columns'] = help_columns |
781 | 786 |
example_data = u','.join(column['name'] + (' key' if column['key'] else '') for column in help_columns) + '\n' |
782 | 787 |
example_url = 'data:text/csv;base64,%s' % base64.b64encode(example_data.encode('utf-8')).decode('ascii') |
tests/test_csv_import.py | ||
---|---|---|
22 | 22 |
import io |
23 | 23 |
import codecs |
24 | 24 | |
25 |
from django.contrib.auth.hashers import make_password, check_password |
|
25 | 26 |
from django.core import mail |
26 | 27 | |
27 | 28 |
from django_rbac.utils import get_role_model |
... | ... | |
564 | 565 |
for row in importer.rows if any(cell.errors for cell in row.cells) |
565 | 566 |
} |
566 | 567 |
assert cell_errors == {} |
568 | ||
569 | ||
570 |
def test_csv_password_hash(profile, user_csv_importer_factory): |
|
571 |
content = '''email key,first_name,last_name,password_hash |
|
572 |
tnoel@entrouvert.com,Thomas,Noël,%s''' |
|
573 |
password_hash = make_password('hop') |
|
574 | ||
575 |
importer = user_csv_importer_factory(content % password_hash) |
|
576 |
assert importer.run() |
|
577 |
thomas = User.objects.get(email='tnoel@entrouvert.com') |
|
578 |
assert check_password('hop', thomas.password) |
|
579 | ||
580 |
password_hash = make_password('test', hasher='sha256') |
|
581 |
importer = user_csv_importer_factory(content % password_hash) |
|
582 |
assert importer.run() |
|
583 |
thomas.refresh_from_db() |
|
584 |
assert check_password('test', thomas.password) |
|
585 | ||
586 |
importer = user_csv_importer_factory(content % 'wrong-format') |
|
587 |
assert importer.run() |
|
588 |
assert importer.has_errors |
|
589 |
assert 'unknown hashing algorithm' in importer.rows[0].cells[-1].errors[0].description |
|
590 | ||
591 |
importer = user_csv_importer_factory(content) |
|
592 |
assert importer.run() |
|
593 |
assert importer.has_errors |
|
594 |
assert 'unknown hashing algorithm' in importer.rows[0].cells[-1].errors[0].description |
|
567 |
- |