Implement login and logout through cookie sessions
This commit is contained in:
parent
77ee7987a2
commit
5b2294b699
|
@ -1,3 +1,4 @@
|
|||
import requests
|
||||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -16,9 +17,48 @@ API_PATHS = {
|
|||
}
|
||||
|
||||
|
||||
def build_absolute_uri(path_name):
|
||||
path = API_PATHS.get(path_name, None)
|
||||
if path is None:
|
||||
raise NoReverseMatch("Not found API path name '{}'".format(path_name))
|
||||
class Orchestra(object):
|
||||
def __init__(self, *args, username=None, password=None, **kwargs):
|
||||
self.base_url = kwargs.pop('base_url', settings.API_BASE_URL)
|
||||
self.username = username
|
||||
self.session = requests.Session()
|
||||
self.auth_token = kwargs.pop("auth_token", None)
|
||||
|
||||
return urllib.parse.urljoin(settings.API_BASE_URL, path)
|
||||
if self.auth_token is None:
|
||||
self.auth_token = self.authenticate(self.username, password)
|
||||
|
||||
def build_absolute_uri(self, path_name):
|
||||
path = API_PATHS.get(path_name, None)
|
||||
if path is None:
|
||||
raise NoReverseMatch(
|
||||
"Not found API path name '{}'".format(path_name))
|
||||
|
||||
return urllib.parse.urljoin(self.base_url, path)
|
||||
|
||||
def authenticate(self, username, password):
|
||||
url = self.build_absolute_uri('token-auth')
|
||||
response = self.session.post(
|
||||
url,
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
|
||||
return response.json().get("token", None)
|
||||
|
||||
def request(self, verb, resource):
|
||||
assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"]
|
||||
url = self.build_absolute_uri(resource)
|
||||
|
||||
verb = getattr(self.session, verb.lower())
|
||||
response = verb(url, headers={"Authorization": "Token {}".format(
|
||||
self.auth_token)}, allow_redirects=False)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
status = response.status_code
|
||||
output = response.json()
|
||||
|
||||
return status, output
|
||||
|
||||
def retrieve_domains(self):
|
||||
status, output = self.request("GET", 'domain-list')
|
||||
return output
|
||||
|
|
38
musician/auth.py
Normal file
38
musician/auth.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.middleware.csrf import rotate_token
|
||||
from django.utils.crypto import constant_time_compare
|
||||
|
||||
SESSION_KEY_TOKEN = '_auth_token'
|
||||
SESSION_KEY_USERNAME = '_auth_username'
|
||||
|
||||
|
||||
def login(request, username, token):
|
||||
"""
|
||||
Persist a user id and a backend in the request. This way a user doesn't
|
||||
have to reauthenticate on every request. Note that data set during
|
||||
the anonymous session is retained when the user logs in.
|
||||
"""
|
||||
if SESSION_KEY_TOKEN in request.session:
|
||||
if request.session[SESSION_KEY_USERNAME] != username:
|
||||
# To avoid reusing another user's session, create a new, empty
|
||||
# session if the existing session corresponds to a different
|
||||
# authenticated user.
|
||||
request.session.flush()
|
||||
else:
|
||||
request.session.cycle_key()
|
||||
|
||||
request.session[SESSION_KEY_TOKEN] = token
|
||||
request.session[SESSION_KEY_USERNAME] = username
|
||||
# if hasattr(request, 'user'):
|
||||
# request.user = user
|
||||
rotate_token(request)
|
||||
|
||||
|
||||
def logout(request):
|
||||
"""
|
||||
Remove the authenticated user's ID from the request and flush their session
|
||||
data.
|
||||
"""
|
||||
request.session.flush()
|
||||
# if hasattr(request, 'user'):
|
||||
# from django.contrib.auth.models import AnonymousUser
|
||||
# request.user = AnonymousUser()
|
|
@ -1,22 +1,8 @@
|
|||
import urllib.parse
|
||||
|
||||
import requests
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
|
||||
from . import api
|
||||
|
||||
|
||||
def authenticate(username, password):
|
||||
url = api.build_absolute_uri('token-auth')
|
||||
r = requests.post(
|
||||
url,
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
|
||||
token = r.json().get("token", None)
|
||||
return token
|
||||
|
||||
|
||||
class LoginForm(AuthenticationForm):
|
||||
|
||||
def clean(self):
|
||||
|
@ -24,10 +10,12 @@ class LoginForm(AuthenticationForm):
|
|||
password = self.cleaned_data.get('password')
|
||||
|
||||
if username is not None and password:
|
||||
self.token = authenticate(username, password)
|
||||
if self.token is None:
|
||||
orchestra = api.Orchestra(username=username, password=password)
|
||||
|
||||
if orchestra.auth_token is None:
|
||||
raise self.get_invalid_login_error()
|
||||
else:
|
||||
return self.token
|
||||
self.username = username
|
||||
self.token = orchestra.auth_token
|
||||
|
||||
return self.cleaned_data
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
</li>
|
||||
<div class="dropdown-divider"></div>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#logout">{% trans 'Log out' %}</a>
|
||||
<a class="nav-link" href="{% url 'musician:logout' %}">{% trans 'Log out' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@ app_name = 'musician'
|
|||
|
||||
urlpatterns = [
|
||||
path('auth/login/', views.LoginView.as_view(), name='login'),
|
||||
# path('auth/logout/', views.LogoutView.as_view(), name='logout'),
|
||||
path('auth/logout/', views.LogoutView.as_view(), name='logout'),
|
||||
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
||||
]
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.base import RedirectView, TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from . import api, get_version
|
||||
from .auth import login as auth_login, logout as auth_logout
|
||||
from .forms import LoginForm
|
||||
from .mixins import CustomContextMixin
|
||||
|
||||
|
@ -18,3 +20,32 @@ class LoginView(FormView):
|
|||
form_class = LoginForm
|
||||
success_url = reverse_lazy('musician:dashboard')
|
||||
extra_context = {'version': get_version()}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['request'] = self.request
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Security check complete. Log the user in."""
|
||||
auth_login(self.request, form.username, form.token)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
class LogoutView(RedirectView):
|
||||
"""
|
||||
Log out the user.
|
||||
"""
|
||||
permanent = False
|
||||
pattern_name = 'musician:login'
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
"""
|
||||
Logs out the user.
|
||||
"""
|
||||
auth_logout(self.request)
|
||||
return super().get_redirect_url(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Logout may be done via POST."""
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
|
|
@ -114,6 +114,14 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
LOGIN_URL = '/auth/login/'
|
||||
|
||||
# Sessions
|
||||
# https://docs.djangoproject.com/en/2.2/topics/http/sessions/#configuring-sessions
|
||||
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
|
||||
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||
|
|
Loading…
Reference in a new issue