Start implementation of OAuth client
This commit is contained in:
parent
faf697d3cf
commit
e2aa645bbf
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -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')
|
||||||
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue