diff --git a/med/settings.py b/med/settings.py
index be9456a..2b89e58 100644
--- a/med/settings.py
+++ b/med/settings.py
@@ -169,6 +169,21 @@ AUTH_USER_MODEL = 'users.User'
MAX_EMPRUNT = 5 # Max emprunts
+# AUTHLIB CLIENTS
+AUTHLIB_OAUTH_CLIENTS = {
+ 'notekfet': {
+ 'client_id': 'qtElmOUj67YNvSZjA5l70ITUMxd3NJ9kksBsK5e9',
+ 'client_secret': 'SwF909sLIeU5GhruXsFzKfdBhFNgs8nvkVpFKgP4pIQ80BmLLlf3ZkMoNL7Cpox6Ke3MXNWGswTtbKkM8AiB9v6pys8PNfYH0MDFWAi3tnffjwaMQBzRFhjx20qb6S4W',
+ 'access_token_url': 'https://note-dev.crans.org/o/token/',
+ 'refresh_token_url': 'https://note-dev.crans.org/o/token/',
+ 'authorize_url': 'https://note-dev.crans.org/o/authorize/',
+ 'userinfo_endpoint': 'https://note-dev.crans.org/api/me/',
+ 'client_kwargs': {
+ 'scope': '1_1 2_1 48_1',
+ }
+ }
+}
+
try:
from .settings_local import *
except ImportError:
diff --git a/requirements.txt b/requirements.txt
index 7f2237e..19a6e17 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+authlib~=0.15
docutils~=0.16 # for Django-admin docs
Django~=2.2
django-filter~=2.4
diff --git a/theme/templates/admin/base_site.html b/theme/templates/admin/base_site.html
index 4adb2d8..5a4d054 100644
--- a/theme/templates/admin/base_site.html
+++ b/theme/templates/admin/base_site.html
@@ -54,7 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if user.is_authenticated %}
{% trans 'Log out' %}
{% else %}
- {% trans 'Log in' %}
+ {% trans 'Log in' %}
{% endif %}
{% endblock %}
diff --git a/users/migrations/0043_accesstoken.py b/users/migrations/0043_accesstoken.py
new file mode 100644
index 0000000..709235c
--- /dev/null
+++ b/users/migrations/0043_accesstoken.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.24 on 2021-11-02 15:11
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0042_delete_adhesion'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AccessToken',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('access_token', models.CharField(max_length=32, verbose_name='access token')),
+ ('expires_in', models.PositiveSmallIntegerField(verbose_name='expires in')),
+ ('scopes', models.CharField(max_length=255, verbose_name='scopes')),
+ ('refresh_token', models.CharField(max_length=32, verbose_name='refresh token')),
+ ('expires_at', models.DateTimeField(verbose_name='expires at')),
+ ('owner', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='owner')),
+ ],
+ options={
+ 'verbose_name': 'access token',
+ 'verbose_name_plural': 'access tokens',
+ },
+ ),
+ ]
diff --git a/users/models.py b/users/models.py
index 3b5cf51..91203ea 100644
--- a/users/models.py
+++ b/users/models.py
@@ -2,6 +2,10 @@
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
+from datetime import datetime
+
+from authlib.integrations.django_client import OAuth
+from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
@@ -44,3 +48,61 @@ class User(AbstractUser):
def is_member(self):
# FIXME Use NK20
return True
+
+
+class AccessToken(models.Model):
+ owner = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ null=True,
+ default=None,
+ verbose_name=_('owner'),
+ )
+
+ access_token = models.CharField(
+ max_length=32,
+ verbose_name=_('access token'),
+ )
+
+ expires_in = models.PositiveSmallIntegerField(
+ verbose_name=_('expires in'),
+ )
+
+ scopes = models.CharField(
+ max_length=255,
+ verbose_name=_('scopes'),
+ )
+
+ refresh_token = models.CharField(
+ max_length=32,
+ verbose_name=_('refresh token'),
+ )
+
+ expires_at = models.DateTimeField(
+ verbose_name=_('expires at'),
+ )
+
+ def refresh(self):
+ """
+ Refresh the access token.
+ """
+ oauth = OAuth()
+ oauth.register('notekfet')
+ # Get the OAuth client
+ oauth_client = oauth.notekfet._get_oauth_client()
+ # Actually refresh the token
+ token = oauth_client.refresh_token(oauth.notekfet.access_token_url,
+ refresh_token=self.refresh_token)
+ self.access_token = token['access_token']
+ self.expires_in = token['expires_in']
+ self.scopes = token['scope']
+ self.refresh_token = token['refresh_token']
+ self.expires_at = timezone.utc.fromutc(
+ datetime.fromtimestamp(token['expires_at'])
+ )
+
+ self.save()
+
+ class Meta:
+ verbose_name = _('access token')
+ verbose_name_plural = _('access tokens')
diff --git a/users/urls.py b/users/urls.py
index 457e218..54fb5f1 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -8,5 +8,6 @@ from . import views
app_name = 'users'
urlpatterns = [
- url(r'^edit_info/$', views.edit_info, name='edit-info'),
+ url('login/', views.LoginView.as_view(), name='login'),
+ url('authorize/', views.AuthorizeView.as_view(), name='auth'),
]
diff --git a/users/views.py b/users/views.py
index 0f5b17a..2a4e7ec 100644
--- a/users/views.py
+++ b/users/views.py
@@ -1,47 +1,42 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
+from datetime import datetime
-from django.contrib import messages
-from django.contrib.auth.decorators import login_required
+from authlib.integrations.django_client import OAuth
from django.contrib.auth.models import Group
-from django.db import transaction
-from django.shortcuts import redirect, render
-from django.template.context_processors import csrf
-from django.utils.translation import ugettext_lazy as _
+from django.urls import reverse
+from django.utils import timezone
+from django.views.generic import RedirectView
from rest_framework import viewsets
-from reversion import revisions as reversion
-from users.forms import BaseInfoForm
-from users.models import User
+from users.models import User, AccessToken
from .serializers import GroupSerializer, UserSerializer
-def form(ctx, template, request):
- c = ctx
- c.update(csrf(request))
- return render(request, template, c)
+class LoginView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ oauth = OAuth()
+ oauth.register('notekfet')
+ redirect_url = self.request.build_absolute_uri(reverse('users:auth'))
+ return oauth.notekfet.authorize_redirect(self.request, redirect_url).url
-@login_required
-def edit_info(request):
- """
- Edite son utilisateur
- """
- user = BaseInfoForm(request.POST or None, instance=request.user)
- if user.is_valid():
- with transaction.atomic(), reversion.create_revision():
- user.save()
- reversion.set_user(request.user)
- reversion.set_comment("Champs modifié(s) : %s" % ', '.join(
- field for field in user.changed_data))
- messages.success(request, "L'user a bien été modifié")
- return redirect("index")
- return form({
- 'form': user,
- 'password_change': True,
- 'title': _('Edit user profile'),
- }, 'users/user.html', request)
+class AuthorizeView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ oauth = OAuth()
+ oauth.register('notekfet')
+ token = oauth.notekfet.authorize_access_token(self.request)
+ token_obj = AccessToken.objects.create(
+ access_token=token['access_token'],
+ expires_in=token['expires_in'],
+ scopes=token['scope'],
+ refresh_token=token['refresh_token'],
+ expires_at=timezone.utc.fromutc(
+ datetime.fromtimestamp(token['expires_at'])),
+ )
+ # TODO Log in or create user
+ return '/'
class UserViewSet(viewsets.ModelViewSet):