mirror of
				https://gitlab.crans.org/mediatek/med.git
				synced 2025-11-04 04:22:19 +01:00 
			
		
		
		
	Start implementation of OAuth client
This commit is contained in:
		@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
authlib~=0.15
 | 
			
		||||
docutils~=0.16  # for Django-admin docs
 | 
			
		||||
Django~=2.2
 | 
			
		||||
django-filter~=2.4
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
            {% if user.is_authenticated %}
 | 
			
		||||
                <a href="{% url 'logout' %}">{% trans 'Log out' %}</a>
 | 
			
		||||
            {% else %}
 | 
			
		||||
                <a href="{% url 'login' %}">{% trans 'Log in' %}</a>
 | 
			
		||||
                <a href="{% url 'users:login' %}">{% trans 'Log in' %}</a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								users/migrations/0043_accesstoken.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								users/migrations/0043_accesstoken.py
									
									
									
									
									
										Normal 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',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -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')
 | 
			
		||||
 
 | 
			
		||||
@@ -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'),
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user