Commit e472f292 authored by Guilhem Saurel's avatar Guilhem Saurel
Browse files

black

parent 078b971a
default_app_config = 'accounts.apps.AccountsConfig'
default_app_config = "accounts.apps.AccountsConfig"
......@@ -5,7 +5,16 @@ from django.contrib.auth.models import Group
class GroupAdmin(AuthGroupAdmin):
def get_fieldsets(self, request, obj=None):
return [(None, {'fields': ['name',]})]
return [
(
None,
{
"fields": [
"name",
]
},
)
]
admin.site.unregister(Group)
......
......@@ -2,7 +2,7 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
name = "accounts"
def ready(self):
import accounts.signals # noqa
......@@ -10,7 +10,9 @@ class EmailBackend(ModelBackend):
try:
user = UserModel._default_manager.get(email__iexact=username)
except UserModel.DoesNotExist:
UserModel().set_password(password) # https://code.djangoproject.com/ticket/20760
UserModel().set_password(
password
) # https://code.djangoproject.com/ticket/20760
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
......@@ -8,19 +8,24 @@ from .models import Profile
class UserForm(ModelForm):
class Meta:
model = User
fields = ('email',)
fields = ("email",)
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ('common_name', 'phone_number', 'address', 'ssh_keys',)
fields = (
"common_name",
"phone_number",
"address",
"ssh_keys",
)
class PasswordResetForm(AuthPasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
email = self.cleaned_data["email"]
users = User.objects.filter(email__iexact=email)
if not users.exists():
raise ValidationError('Aucun utilisateur connu avec cette adresse e-mail.')
raise ValidationError("Aucun utilisateur connu avec cette adresse e-mail.")
return email
......@@ -6,7 +6,7 @@ import base64
class PBKDF2DK256PasswordHasher(PBKDF2PasswordHasher):
algorithm = 'pbkdf2_sha256_dk256'
algorithm = "pbkdf2_sha256_dk256"
iterations = 2048
digest = hashlib.sha256
dklen = 256
......@@ -16,8 +16,8 @@ class PBKDF2DK256PasswordHasher(PBKDF2PasswordHasher):
def encode(self, password, salt, iterations=None):
assert password is not None
assert salt and '$' not in salt
assert salt and "$" not in salt
iterations = iterations or self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest, dklen=self.dklen)
hash = base64.b64encode(hash).decode('ascii').strip()
hash = base64.b64encode(hash).decode("ascii").strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
......@@ -14,21 +14,48 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('adhesions', '0005_auto_20170206_0039'),
("adhesions", "0005_auto_20170206_0039"),
]
state_operations = [
migrations.CreateModel(
name='Profile',
name="Profile",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', models.CharField(blank=True, default='', max_length=16, verbose_name='Numéro de téléphone')),
('address', models.TextField(blank=True, default='', verbose_name='Adresse')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='Utilisateur')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"phone_number",
models.CharField(
blank=True,
default="",
max_length=16,
verbose_name="Numéro de téléphone",
),
),
(
"address",
models.TextField(blank=True, default="", verbose_name="Adresse"),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
),
],
options={
'verbose_name': 'profil',
'db_table': 'accounts_profile',
"verbose_name": "profil",
"db_table": "accounts_profile",
},
bases=(models.Model,),
),
......
......@@ -8,12 +8,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
("accounts", "0001_initial"),
]
operations = [
migrations.AlterModelTable(
name='profile',
name="profile",
table=None,
),
]
......@@ -8,13 +8,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20170206_2358'),
("accounts", "0002_auto_20170206_2358"),
]
operations = [
migrations.AddField(
model_name='profile',
name='notes',
field=models.TextField(blank=True, default=''),
model_name="profile",
name="notes",
field=models.TextField(blank=True, default=""),
),
]
......@@ -9,14 +9,19 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_profile_notes'),
('adhesions', '0011_auto_20170517_2301'),
("accounts", "0003_profile_notes"),
("adhesions", "0011_auto_20170517_2301"),
]
operations = [
migrations.AlterField(
model_name='profile',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to='adhesions.User', verbose_name='Utilisateur'),
model_name="profile",
name="user",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to="adhesions.User",
verbose_name="Utilisateur",
),
),
]
......@@ -8,13 +8,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_auto_20170517_2301'),
("accounts", "0004_auto_20170517_2301"),
]
operations = [
migrations.AddField(
model_name='profile',
name='ssh_keys',
field=models.TextField(blank=True, default=''),
model_name="profile",
name="ssh_keys",
field=models.TextField(blank=True, default=""),
),
]
......@@ -8,13 +8,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_profile_ssh_keys'),
("accounts", "0005_profile_ssh_keys"),
]
operations = [
migrations.AlterField(
model_name='profile',
name='ssh_keys',
field=models.TextField(blank=True, default='', verbose_name='Clefs SSH'),
model_name="profile",
name="ssh_keys",
field=models.TextField(blank=True, default="", verbose_name="Clefs SSH"),
),
]
......@@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_auto_20170607_2306'),
("accounts", "0006_auto_20170607_2306"),
]
operations = [
migrations.AddField(
model_name='profile',
name='common_name',
field=models.CharField(blank=True, default='', max_length=32, verbose_name='Nom d’usage'),
model_name="profile",
name="common_name",
field=models.CharField(
blank=True, default="", max_length=32, verbose_name="Nom d’usage"
),
),
]
......@@ -6,24 +6,37 @@ from adhesions.models import Adhesion, User, Corporation
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile', verbose_name='Utilisateur', on_delete=models.CASCADE)
common_name = models.CharField(max_length=32, blank=True, default='', verbose_name='Nom d’usage')
phone_number = models.CharField(max_length=16, blank=True, default='',
verbose_name='Numéro de téléphone')
address = models.TextField(blank=True, default='', verbose_name='Adresse')
ssh_keys = models.TextField(blank=True, default='', verbose_name='Clefs SSH')
notes = models.TextField(blank=True, default='')
user = models.OneToOneField(
User,
related_name="profile",
verbose_name="Utilisateur",
on_delete=models.CASCADE,
)
common_name = models.CharField(
max_length=32, blank=True, default="", verbose_name="Nom d’usage"
)
phone_number = models.CharField(
max_length=16, blank=True, default="", verbose_name="Numéro de téléphone"
)
address = models.TextField(blank=True, default="", verbose_name="Adresse")
ssh_keys = models.TextField(blank=True, default="", verbose_name="Clefs SSH")
notes = models.TextField(blank=True, default="")
@property
def email(self):
return self.user.email
class Meta:
verbose_name = 'profil'
verbose_name = "profil"
@property
def adhesions(self): # user and corporations (for which the user is a member) adhesions
return Adhesion.objects.filter(models.Q(user__pk=self.user.pk) | models.Q(corporation__members__profile__pk=self.pk))
def adhesions(
self,
): # user and corporations (for which the user is a member) adhesions
return Adhesion.objects.filter(
models.Q(user__pk=self.user.pk)
| models.Q(corporation__members__profile__pk=self.pk)
)
def __str__(self):
return self.common_name or self.user.get_full_name() or self.user.username
......@@ -7,19 +7,19 @@ from .models import Profile
from adhesions.models import User
@receiver(pre_save, sender=User, dispatch_uid='set_unusable_password')
@receiver(pre_save, sender=User, dispatch_uid="set_unusable_password")
def set_unusable_password(sender, instance, **kwargs):
if not instance.password:
instance.set_password(get_random_string(length=32))
@receiver(pre_save, sender=User, dispatch_uid='set_default_username')
@receiver(pre_save, sender=User, dispatch_uid="set_default_username")
def set_default_username(sender, instance, **kwargs):
if not instance.username:
instance.username = slugify(instance.get_full_name())
@receiver(post_save, sender=User, dispatch_uid='create_profile')
@receiver(post_save, sender=User, dispatch_uid="create_profile")
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
......@@ -8,46 +8,69 @@ from .forms import UserForm, ProfileForm
class ViewsTestCase(TestCase):
def setUp(self):
user = User.objects.create_user('user', first_name='first', last_name='last', email='user@example.net', password='user')
user = User.objects.create_user(
"user",
first_name="first",
last_name="last",
email="user@example.net",
password="user",
)
def test_auth(self):
self.assertEquals(self.client.get(reverse('login')).status_code, 200)
self.assertEquals(self.client.get(reverse('password_reset')).status_code, 200)
self.assertEquals(self.client.get(reverse('password_reset_done')).status_code, 200)
self.assertEquals(self.client.get(reverse("login")).status_code, 200)
self.assertEquals(self.client.get(reverse("password_reset")).status_code, 200)
self.assertEquals(
self.client.get(reverse("password_reset_done")).status_code, 200
)
def test_email_backend(self):
self.assertFalse(self.client.login(username='user@example.net', password='wrong'))
self.assertFalse(self.client.login(username='wrong@example.net', password='user'))
self.assertTrue(self.client.login(username='user@example.net', password='user'))
self.assertFalse(
self.client.login(username="user@example.net", password="wrong")
)
self.assertFalse(
self.client.login(username="wrong@example.net", password="user")
)
self.assertTrue(self.client.login(username="user@example.net", password="user"))
def test_login_logout(self):
self.assertEquals(self.client.get(reverse('login')).status_code, 200)
self.client.login(username='user', password='user')
self.assertEquals(self.client.get(reverse('adhesion-detail-user')).status_code, 200)
self.assertRedirects(self.client.get(reverse('logout')), reverse('adhesion-detail-user'), target_status_code=302) # user page is redirected to login
self.assertRedirects(self.client.get(reverse('adhesion-detail-user')), reverse('login') + '?next=' + reverse('adhesion-detail-user'))
self.assertEquals(self.client.get(reverse("login")).status_code, 200)
self.client.login(username="user", password="user")
self.assertEquals(
self.client.get(reverse("adhesion-detail-user")).status_code, 200
)
self.assertRedirects(
self.client.get(reverse("logout")),
reverse("adhesion-detail-user"),
target_status_code=302,
) # user page is redirected to login
self.assertRedirects(
self.client.get(reverse("adhesion-detail-user")),
reverse("login") + "?next=" + reverse("adhesion-detail-user"),
)
def test_profile(self):
response = self.client.get(reverse('profile'))
self.assertRedirects(response, reverse('login') + '?next=' + reverse('profile'))
self.client.login(username='user', password='user')
response = self.client.get(reverse('profile'))
response = self.client.get(reverse("profile"))
self.assertRedirects(response, reverse("login") + "?next=" + reverse("profile"))
self.client.login(username="user", password="user")
response = self.client.get(reverse("profile"))
self.assertEqual(response.status_code, 200)
user = User.objects.get(username='user')
user = User.objects.get(username="user")
user_form = UserForm(None, instance=user)
data = {key: getattr(user_form.instance, key) for key in user_form.fields}
profile_form = ProfileForm(instance=user.profile)
data.update({key: getattr(profile_form.instance, key) for key in profile_form.fields})
data['username'] = 'user2' # try to tamper username
data['first_name'] = 'first2' # try to tamper username
data['last_name'] = 'last2' # try to tamper username
data['email'] = 'user@example.org'
data['address'] = '221B Baker Street'
response = self.client.post(reverse('profile'), data)
self.assertRedirects(response, reverse('profile'))
user = User.objects.get(pk=user.pk) # refresh user
self.assertEquals(user.username, 'user') # should not be modified
self.assertEquals(user.first_name, 'first') # should not be modified
self.assertEquals(user.last_name, 'last') # should not be modified
self.assertEquals(user.email, 'user@example.org')
self.assertEquals(user.profile.address, '221B Baker Street')
data.update(
{key: getattr(profile_form.instance, key) for key in profile_form.fields}
)
data["username"] = "user2" # try to tamper username
data["first_name"] = "first2" # try to tamper username
data["last_name"] = "last2" # try to tamper username
data["email"] = "user@example.org"
data["address"] = "221B Baker Street"
response = self.client.post(reverse("profile"), data)
self.assertRedirects(response, reverse("profile"))
user = User.objects.get(pk=user.pk) # refresh user
self.assertEquals(user.username, "user") # should not be modified
self.assertEquals(user.first_name, "first") # should not be modified
self.assertEquals(user.last_name, "last") # should not be modified
self.assertEquals(user.email, "user@example.org")
self.assertEquals(user.profile.address, "221B Baker Street")
......@@ -7,10 +7,14 @@ from .forms import PasswordResetForm
urlpatterns = [
url(r'^profile/$', views.profile, name='profile'),
url(r'^auth_api/(?P<token>[a-zA-Z0-9]{32})/$', views.auth_api, name='auth_api'),
url(r'^password_reset/$', auth_views.PasswordResetView.as_view(form_class=PasswordResetForm), name='password_reset'),
url(r'^login/$', views.login, name='login'),
url(r'^logout/$', views.logout, name='logout'),
url(r'^', include('django.contrib.auth.urls')),
url(r"^profile/$", views.profile, name="profile"),
url(r"^auth_api/(?P<token>[a-zA-Z0-9]{32})/$", views.auth_api, name="auth_api"),
url(
r"^password_reset/$",
auth_views.PasswordResetView.as_view(form_class=PasswordResetForm),
name="password_reset",
),
url(r"^login/$", views.login, name="login"),
url(r"^logout/$", views.logout, name="logout"),
url(r"^", include("django.contrib.auth.urls")),
]
......@@ -20,11 +20,11 @@ from services.utils.ip_conversion import ipv6_to_ipv4
def login(request):
def try_authenticate_from_ip():
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
ip = x_forwarded_for.split(",")[0]
else:
ip = request.META.get('REMOTE_ADDR')
ip = request.META.get("REMOTE_ADDR")
try:
ip = ip_address(ip)
except ValueError:
......@@ -39,12 +39,13 @@ def login(request):
if not allocation:
return
user = allocation.service.adhesion.user
if not user: # corporation
if not user: # corporation
return
if user.is_superuser: # superusers must enter password
if user.is_superuser: # superusers must enter password
return
auth_login(request, user, backend='django.contrib.auth.backends.ModelBackend')
if not request.session.get('disable-ip-based-login', False):
auth_login(request, user, backend="django.contrib.auth.backends.ModelBackend")
if not request.session.get("disable-ip-based-login", False):
try_authenticate_from_ip()
# Même si l’auto-login a marché, on utilise LoginView qui fera la bonne redirection
return LoginView.as_view(redirect_authenticated_user=True)(request)
......@@ -53,7 +54,7 @@ def login(request):
def logout(request):
response = LogoutView.as_view()(request)
# Définie *après* car le logout flush la session
request.session['disable-ip-based-login'] = True
request.session["disable-ip-based-login"] = True
return response
......@@ -62,37 +63,41 @@ def profile(request):
user_form = UserForm(request.POST or None, instance=request.user)
profile_form = ProfileForm(request.POST or None, instance=request.user.profile)
forms = [user_form, profile_form]
if request.method == 'POST' and all(form.is_valid() for form in forms):
if request.method == "POST" and all(form.is_valid() for form in forms):
for form in forms:
form.save()
messages.success(request, 'Profil mis à jour avec succès !')
return redirect('profile')
return render(request, 'accounts/profile.html', {
'user_form': user_form,
'profile_form': profile_form,
})
messages.success(request, "Profil mis à jour avec succès !")
return redirect("profile")
return render(
request,
"accounts/profile.html",
{
"user_form": user_form,
"profile_form": profile_form,
},
)
@require_POST
@csrf_exempt
def auth_api(request, token):
# token could not be None due to url regex
if token != getattr(settings, 'AUTH_API_TOKEN', None):
if token != getattr(settings, "AUTH_API_TOKEN", None):
raise PermissionDenied
username = request.POST.get('username')
username = request.POST.get("username")
if not username:
return HttpResponseBadRequest()
password = request.POST.get('password')
password = request.POST.get("password")
if password:
user = authenticate(username=username, password=password)
if user is None:
return HttpResponse('<h1>401 Unauthorized</h1>', status=401)
return HttpResponse("<h1>401 Unauthorized</h1>", status=401)
else:
user = get_object_or_404(get_user_model(), username=username)
required_groups = request.POST.get('groups')
if required_groups and not user.is_superuser: # skip groups check for superusers
required_groups = set(required_groups.split(' '))
required_groups = request.POST.get("groups")
if required_groups and not user.is_superuser: # skip groups check for superusers
required_groups = set(required_groups.split(" "))
user_groups = set(map(lambda g: g.name, user.groups.all()))
if required_groups - user_groups:
return HttpResponse('<h1>401 Unauthorized</h1>', status=401)
return HttpResponse("<h1>401 Unauthorized</h1>", status=401)
return HttpResponse()
default_app_config = 'adhesions.apps.AdhesionsConfig'
default_app_config = "adhesions.apps.AdhesionsConfig"
......@@ -23,6 +23,7 @@ from datetime import timedelta
### Inlines
class ProfileInline(admin.StackedInline):
model = Profile
......@@ -35,7 +36,10 @@ class ProfileInline(admin.StackedInline):
class AdhesionInline(admin.StackedInline):
model = Adhesion
fields = ('id', 'notes',)
fields = (
"id",
"notes",
)
def has_add_permission(self, request, obj):
return False
......@@ -48,8 +52,14 @@ class ServiceInline(admin.StackedInline):
model = Service
extra = 0
show_change_link = True
fields = ('service_type'