Start implementation of OAuth client

This commit is contained in:
Yohann D'ANELLO 2021-11-04 11:29:03 +01:00
parent faf697d3cf
commit e2aa645bbf
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
7 changed files with 139 additions and 34 deletions

View File

@ -169,6 +169,21 @@ AUTH_USER_MODEL = 'users.User'
MAX_EMPRUNT = 5 # Max emprunts 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: try:
from .settings_local import * from .settings_local import *
except ImportError: except ImportError:

View File

@ -1,3 +1,4 @@
authlib~=0.15
docutils~=0.16 # for Django-admin docs docutils~=0.16 # for Django-admin docs
Django~=2.2 Django~=2.2
django-filter~=2.4 django-filter~=2.4

View File

@ -54,7 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a href="{% url 'logout' %}">{% trans 'Log out' %}</a> <a href="{% url 'logout' %}">{% trans 'Log out' %}</a>
{% else %} {% else %}
<a href="{% url 'login' %}">{% trans 'Log in' %}</a> <a href="{% url 'users:login' %}">{% trans 'Log in' %}</a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -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',
},
),
]

View File

@ -2,6 +2,10 @@
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay # Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # 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.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -44,3 +48,61 @@ class User(AbstractUser):
def is_member(self): def is_member(self):
# FIXME Use NK20 # FIXME Use NK20
return True 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')

View File

@ -8,5 +8,6 @@ from . import views
app_name = 'users' app_name = 'users'
urlpatterns = [ 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'),
] ]

View File

@ -1,47 +1,42 @@
# -*- mode: python; coding: utf-8 -*- # -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay # Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime
from django.contrib import messages from authlib.integrations.django_client import OAuth
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.db import transaction from django.urls import reverse
from django.shortcuts import redirect, render from django.utils import timezone
from django.template.context_processors import csrf from django.views.generic import RedirectView
from django.utils.translation import ugettext_lazy as _
from rest_framework import viewsets from rest_framework import viewsets
from reversion import revisions as reversion from users.models import User, AccessToken
from users.forms import BaseInfoForm
from users.models import User
from .serializers import GroupSerializer, UserSerializer from .serializers import GroupSerializer, UserSerializer
def form(ctx, template, request): class LoginView(RedirectView):
c = ctx def get_redirect_url(self, *args, **kwargs):
c.update(csrf(request)) oauth = OAuth()
return render(request, template, c) 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 class AuthorizeView(RedirectView):
def edit_info(request): def get_redirect_url(self, *args, **kwargs):
""" oauth = OAuth()
Edite son utilisateur oauth.register('notekfet')
""" token = oauth.notekfet.authorize_access_token(self.request)
user = BaseInfoForm(request.POST or None, instance=request.user) token_obj = AccessToken.objects.create(
if user.is_valid(): access_token=token['access_token'],
with transaction.atomic(), reversion.create_revision(): expires_in=token['expires_in'],
user.save() scopes=token['scope'],
reversion.set_user(request.user) refresh_token=token['refresh_token'],
reversion.set_comment("Champs modifié(s) : %s" % ', '.join( expires_at=timezone.utc.fromutc(
field for field in user.changed_data)) datetime.fromtimestamp(token['expires_at'])),
messages.success(request, "L'user a bien été modifié") )
return redirect("index") # TODO Log in or create user
return form({ return '/'
'form': user,
'password_change': True,
'title': _('Edit user profile'),
}, 'users/user.html', request)
class UserViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet):