Commit 881207ec authored by Alexandre Terrien's avatar Alexandre Terrien

Authentication Backend not working

parent f71a198b
Opérations effectuées sur le projet :
Migration 001 : accounts.Account
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
from django.contrib.auth import get_user_model
import requests
User = get_user_model()
AUTH_EISTIENS_NET_URL = 'http://auth.eistiens.net'
class AuthEistiensNetBackend():
def authenticate(self, username, password):
response = requests.post(
AUTH_EISTIENS_NET_URL,
data={'username': username, 'password': password}
)
if response.status_code == 200:
try:
u = User.objects.get(username=username)
return User.objects.get(username=username)
except User.DoesNotExist:
user = User.objects.create_user(
username=username,
password=password
)
user_data = response.json()['user']
user.last_name = user_data['last_name']
user.first_name = user_data['first_name']
user.email = user_data['email']
user.backend = accounts.AuthEistiensNetBackend
user.save()
return user
def get_user(self, username):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class CustomAuthenticationForm(AuthenticationForm):
username = forms.CharField(
max_length=254,
widget=forms.TextInput(attrs={
'autofocus': '',
'placeholder': 'Login LDAP'
})
)
password = forms.CharField(
label="Mot de passe",
strip=False,
widget=forms.PasswordInput(attrs={
'placeholder': 'Mot de passe LDAP'
})
)
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-03 12:46
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-10 14:08
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='account',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='default_user'),
),
]
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
class Account(models.Model):
user = models.OneToOneField(User, verbose_name='default_user')
def create_user_profile(sender, instance, created, **kwargs):
if created:
Account.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
{% extends "base.html" %}
{% block header_text %}Se connecter{% endblock %}
{% block content-block %}
{% if form.errors %}
<p>Erreur lors de la connexion</p>
{% endif %}
{% if next %}
<p> Vous avez besoin d'être connecté pour voir cette page </p>
{% endif %}
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
{{ form.username.label_tag }}
{{ form.username }}<br/>
{{ form.password.label_tag }}
{{ form.password }}<br/>
<input type="submit" value="login"/>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
{% endblock %}
from accounts.authentication import (
AUTH_EISTIENS_NET_URL, AuthEistiensNetBackend
)
from django.contrib.auth import get_user_model
from django.test import TestCase
from unittest.mock import patch
User = get_user_model()
@patch('accounts.authentication.requests.post')
class AuthenticateTest(TestCase):
def setUp(self):
self.backend = AuthEistiensNetBackend()
self.user = User.objects.create_user(username='test', password='test')
self.user.last_name = 'Tester'
self.user.first_name = 'Testing'
self.user.email = 'test@eisti.fr'
self.user.save()
def test_sends_request_to_auth(self, mock_post):
self.backend.authenticate(username='test', password='test')
mock_post.assert_called_once_with(
AUTH_EISTIENS_NET_URL,
data={'username': 'test', 'password': 'test'}
)
def test_returns_none_when_403(self, mock_post):
mock_post.return_value.status_code = 403
response = self.backend.authenticate(username='', password='')
self.assertIsNone(response)
def test_finds_existing_user(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {
"status": "ok",
"user": {
"first_name": "First",
"last_name": "Last",
"email": "other@eisti.fr",
"employee_number": 0
}
}
other_user = User.objects.create_user(username='other', password='pw')
user = self.backend.authenticate(username='other', password='pw')
self.assertEquals(user, other_user)
def test_creates_new_user_if_necessary(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {
"status": "ok",
"user": {
"first_name": "Goddy",
"last_name": "God",
"email": "god@eisti.fr",
"employee_number": 0
}
}
found_user = self.backend.authenticate(username='god', password='meh')
new_user = User.objects.get(username='god')
self.assertEquals(new_user, found_user)
class GetUserTest(TestCase):
def test_get_user_by_username(self):
backend = AuthEistiensNetBackend()
bad_user = User.objects.create_user(username='badOne', password='pw')
bad_user.save()
desired_user = User.objects.create_user(username='user', password='pa')
found_user = backend.get_user('user')
self.assertEqual(desired_user, found_user)
def test_returns_none_if_no_user_with_that_username(self):
backend = AuthEistiensNetBackend()
self.assertIsNone(backend.get_user('whaterver'))
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class UserModelTest(TestCase):
def test_user_model_has_username_and_password(self):
user = User('test', 'test')
self.assertTrue(hasattr(user, 'username'))
self.assertTrue(hasattr(user, 'password'))
from django.conf.urls import url
from django.contrib.auth.views import logout, login
from accounts import views
from accounts.forms import CustomAuthenticationForm
urlpatterns = [
# url(r'^login$', views.login, name='login'),
url(
r'^login$',
login,
{
'template_name': 'login.html',
'authentication_form': CustomAuthenticationForm
},
name='login'
),
url(r'^failed_login$', views.failed_login, name='failed_login'),
url(r'^logout$', logout, {'next_page': '/'}, name='logout'),
]
from django.shortcuts import render, redirect
from django.contrib import auth
from django.http import HttpResponseRedirect
# Create your views here.
def login(request):
user = auth.authenticate(
username=request.POST['login'],
password=request.POST['password']
)
if user is not None:
auth.login(request, user)
return redirect('/')
else:
return HttpResponseRedirect('/accounts/failed_login')
def login_page(request):
return render(request, 'login.html')
def failed_login(request):
return render(
request,
'login.html',
{
'request': request,
'login_failed': True,
})
......@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/1.9/ref/settings/
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
......@@ -37,6 +38,8 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portal',
'accounts',
]
MIDDLEWARE_CLASSES = [
......@@ -82,31 +85,20 @@ DATABASES = {
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Authentication setup
AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.ModelBackend',
'accounts.authentication.AuthEistiensNetBackend',
)
LOGIN_URL = "/accounts/login"
LOGIN_REDIRECT_URL = "/"
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'fr-FR'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
......
......@@ -13,9 +13,12 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.conf.urls import include, url
from django.contrib import admin
from portal import views as portal_views
urlpatterns = [
url(r'^$', portal_views.home_page, name='portal'),
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('accounts.urls')),
]
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
import sys
class FunctionalTest(StaticLiveServerTestCase):
@classmethod
def setUpClass(cls):
for arg in sys.argv:
if 'liveserver' in arg:
cls.server_url = 'http://' + arg.split('=')[1]
return
super().setUpClass()
cls.server_url = cls.live_server_url
@classmethod
def tearDownClass(cls):
if cls.server_url == cls.live_server_url:
super().tearDownClass()
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
def tearDown(self):
self.browser.quit()
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from .base import FunctionalTest
class LoginTest(FunctionalTest):
def wait_for_element_with_id(self, id):
WebDriverWait(self.browser, timeout=5).until(
lambda b: b.find_element_by_id(id),
'Could not find element with id {}. Page text was :\n{}'.format(
id, self.browser.find_element_by_tag_name('body').text
)
)
def wait_to_be_logged_in(self):
self.wait_for_element_with_id('id_logout')
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertIn('terrienale', navbar.text)
def wait_to_be_logged_out(self):
self.wait_for_element_with_id('id_login')
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertNotIn('terrienale', navbar.text)
def test_login_LDAP(self):
# The student goes in the brand new website
self.browser.get(self.server_url)
# He notices it's called eistiens.net
self.assertIn('eistiens.net', self.browser.title)
# He notices a link where he can connect
link_text = self.browser.find_element_by_tag_name('a')
self.assertIn('Se connecter', link_text.text)
link_text.click()
# He sees a from where he can fill in his LDAP login
login = self.browser.find_element_by_id('id_username')
passwd = self.browser.find_element_by_id('id_password')
self.assertEqual(login.get_attribute('placeholder'), 'Login LDAP')
self.assertEqual(
passwd.get_attribute('placeholder'),
'Mot de passe LDAP'
)
# He fills in the info
login.send_keys('terrienale')
passwd.send_keys('E77958:terri')
passwd.send_keys(Keys.ENTER)
# He notices his LDAP login on the screen
self.wait_to_be_logged_in()
# He stays logged in even if he reloads
self.browser.refresh()
self.wait_to_be_logged_in()
# He then logs out
self.browser.find_element_by_id('id_logout').click()
self.wait_to_be_logged_out()
# Even if he refreshes, he's still logged out
self.browser.refresh()
self.wait_to_be_logged_out
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class PortalConfig(AppConfig):
name = 'portal'
from django.db import models
# Create your models here.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>eistiens.net</title>
</head>
<body>
<nav class="navbar">{{ user.username }}</nav>
<h1>{% block header_text %}{% endblock %}</h1>
{% block content-block %}{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block header_text %}Page d'accueil{% endblock %}
{% block content-block %}
{% if request.user.is_authenticated %}
<a id='id_logout' href='{% url 'logout' %}'> Se déconnecter</a>
{% else %}
<a id='id_login' href='{% url 'login' %}'>Se connecter</a>
{% endif %}
{% endblock %}
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.core.urlresolvers import reverse
User = get_user_model()
class HomePageTest(TestCase):
def test_home_page_renders_home_template(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
def test_home_page_displays_logout_link_when_logged_in(self):
user = User.objects.create_user(username='test', password='test')
print(self.client.login(username='terrienale', password='E77958:terri'))
response = self.client.get('/')
self.assertContains(
response,
"<a id='id_logout' href='%s'>Se déconnecter" % reverse('logout'),
html=True
)
def test_home_page_displays_login_link_when_not_logged_in(self):
response = self.client.get('/')
self.assertContains(
response,
"<a id='id_login' href='%s'>Se connecter" % reverse('login'),
html=True
)
def test_home_page_displays_username_when_logged_in(self):
user = User.objects.create_user(username='test', password='test')
# print(self.client.login(username='test', password='test'))
response = self.client.get('/')
self.assertContains(
response,
"<nav class='navbar'>%s" % user.username,
html=True
)
from django.shortcuts import render
def home_page(request):
return render(request, 'home.html')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment