Compare commits

...

10 Commits

Author SHA1 Message Date
Yohann D'ANELLO b4fd13a897
stash
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2021-01-28 22:12:35 +01:00
Yohann D'ANELLO 546e936f27 authlib was missing in tests 2020-11-16 04:20:08 +01:00
Yohann D'ANELLO eb0c14d0cf Django crispy forms was missing in tests 2020-11-16 04:17:24 +01:00
Yohann D'ANELLO d63adcd1ee Run test 2020-11-16 04:13:20 +01:00
Yohann D'ANELLO 60d50a6c07 requesting email is useless 2020-11-16 04:10:36 +01:00
Yohann D'ANELLO e4bfcf6043 remove unusued dependency 2020-11-16 04:08:25 +01:00
Yohann D'ANELLO 50249f2ba2 Superusers are also staff 2020-11-16 04:07:19 +01:00
Yohann D'ANELLO c02380a1e4 Define superusers 2020-11-16 04:06:46 +01:00
Yohann D'ANELLO fc18f93ff8 Automatically register users 2020-11-16 04:00:23 +01:00
Yohann D'ANELLO 358f8d446a Use Discord oauth to log in 2020-11-16 03:33:48 +01:00
13 changed files with 223 additions and 13 deletions

View File

@ -1,6 +1,21 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import Action, CupidonAction, DoveAction, HackerAction, RavenAction, WerewolfAction, WitchAction
from .models import Action, Cupidon, CupidonAction, DiscordUser, Dove, DoveAction, Hacker, HackerAction, \
Raven, RavenAction, Werewolf, WerewolfAction, Witch, WitchAction
class PlayerForm(forms.Form):
user = forms.ModelChoiceField(
queryset=DiscordUser.objects.filter(is_superuser=False, player__isnull=True),
label=lambda: _("user").capitalize(),
)
player_type = forms.ChoiceField(
choices=[(model.__name__, model._meta.verbose_name)
for model in [Werewolf, Cupidon, Witch, Raven, Dove, Hacker]],
label=lambda: _("player type").capitalize(),
)
class ActionForm(forms.ModelForm):

View File

@ -1,12 +1,47 @@
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel
class DiscordUser(AbstractUser):
discord_id = models.CharField(
max_length=32,
primary_key=True,
verbose_name=_("discord id"),
)
discriminator = models.CharField(
max_length=4,
verbose_name=_("discriminator"),
)
avatar_id = models.CharField(
max_length=32,
verbose_name=_("avatar id"),
)
@property
def avatar_url(self) -> str:
return f"https://cdn.discordapp.com/avatars/{self.discord_id}/{self.avatar_id}.png"
@property
def plays(self) -> bool:
return Player.objects.filter(user=self).exists()
@property
def role(self):
if self.plays:
return self.player._meta.verbose_name
elif self.is_superuser:
return _("admin")
else:
return _("spectator")
class Player(PolymorphicModel):
user = models.OneToOneField(
User,
DiscordUser,
on_delete=models.CASCADE,
verbose_name=_("user"),
)

8
lg/urls.py Normal file
View File

@ -0,0 +1,8 @@
from django.urls import path
from lg import views
app_name = 'lg'
urlpatterns = [
path('new_player', views.NewPlayerView.as_view()),
]

View File

@ -1,3 +1,13 @@
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import FormView
# Create your views here.
from lg.forms import PlayerForm
class NewPlayerView(LoginRequiredMixin, FormView):
form_class = PlayerForm
template_name = "lg/new_payer.html"
def form_valid(self, form):
user = form.cleaned_data["user"]
class_name

90
lglog/middlewares.py Normal file
View File

@ -0,0 +1,90 @@
from authlib.integrations.base_client import OAuthError
from authlib.integrations.django_client import OAuth
from authlib.oauth2.rfc6749 import OAuth2Token
from django.conf import settings
from django.contrib.auth import login
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin
from lg.models import DiscordUser
class OAuthMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
super().__init__(get_response)
self.oauth = OAuth()
def process_request(self, request):
if settings.OAUTH_URL_WHITELISTS is not None:
for w in settings.OAUTH_URL_WHITELISTS:
if request.path.startswith(w):
return self.get_response(request)
def update_token(token, refresh_token, access_token):
request.session['token'] = token
return None
sso_client = self.oauth.register(
settings.OAUTH_CLIENT_NAME, overwrite=True, **settings.OAUTH_CLIENT, update_token=update_token
)
if request.path.startswith('/oauth/callback'):
self.clear_session(request)
request.session['token'] = sso_client.authorize_access_token(request)
if self.get_current_user(sso_client, request) is not None:
redirect_uri = request.session.pop('redirect_uri', None)
if redirect_uri is not None:
return redirect(redirect_uri)
return redirect("index")
if request.session.get('token', None) is not None:
current_user = self.get_current_user(sso_client, request)
login(request, current_user)
if current_user is not None:
return self.get_response(request)
# remember redirect URI for redirecting to the original URL.
request.session['redirect_uri'] = request.path
return sso_client.authorize_redirect(request, settings.OAUTH_CLIENT['redirect_uri'])
# fetch current login user info
# 1. check if it's in cache
# 2. fetch from remote API when it's not in cache
@staticmethod
def get_current_user(sso_client, request):
token = request.session.get('token', None)
if token is None or 'access_token' not in token:
return None
if not OAuth2Token.from_dict(token).is_expired() and 'user' in request.session:
return DiscordUser.objects.get(discord_id=request.session['user'])
try:
res = sso_client.get(settings.OAUTH_CLIENT['userinfo_endpoint'], token=OAuth2Token(token))
if res.ok:
user_json = res.json()
discord_id = user_json['id']
user, _ = DiscordUser.objects.get_or_create(discord_id=discord_id)
user.username = user_json['username']
user.discriminator = user_json['discriminator']
user.avatar_id = user_json['avatar']
user.is_staff = user.is_superuser = f"{user.username}#{user.discriminator}" in settings.SUPERUSERS
user.save()
request.session['user'] = user.discord_id
return user
raise Exception(res, str(res.__dict__))
except OAuthError as e:
print(e)
return None
@staticmethod
def clear_session(request):
try:
del request.session['user']
del request.session['token']
except KeyError:
pass
def __del__(self):
print('destroyed')

6
lglog/secrets_example.py Normal file
View File

@ -0,0 +1,6 @@
OAUTH_CLIENT_ID = 'CHANGE_ME'
OAUTH_CLIENT_SECRET = 'CHANGE_ME'
SUPERUSERS = [
'ÿnérant#0719',
]

View File

@ -12,10 +12,14 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
from pathlib import Path
try:
from .secrets import OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, SUPERUSERS
except ImportError:
from .secrets_example import OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, SUPERUSERS
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
@ -27,7 +31,6 @@ DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
@ -53,6 +56,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'lglog.middlewares.OAuthMiddleware',
]
ROOT_URLCONF = 'lglog.urls'
@ -75,7 +79,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'lglog.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
@ -86,7 +89,6 @@ DATABASES = {
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
@ -105,13 +107,13 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en'
from django.utils.translation import gettext_lazy as _
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
@ -125,7 +127,6 @@ USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
@ -141,3 +142,27 @@ STATICFILES_DIRS = [
CRISPY_TEMPLATE_PACK = 'bootstrap4'
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html'
AUTH_USER_MODEL = 'lg.discorduser'
# OAuth Settings
OAUTH_URL_WHITELISTS = [
]
OAUTH_CLIENT_NAME = 'discord'
OAUTH_CLIENT = {
'client_id': OAUTH_CLIENT_ID,
'client_secret': OAUTH_CLIENT_SECRET,
'access_token_url': 'https://discordapp.com/api/oauth2/token',
'authorize_url': 'https://discordapp.com/api/oauth2/authorize',
'api_base_url': 'https://discordapp.com/api/',
'redirect_uri': 'http://localhost:8000/oauth/callback',
'client_kwargs': {
'scope': 'identify',
'token_placement': 'header'
},
'userinfo_endpoint': 'users/@me',
}
SUPERUSERS = SUPERUSERS

View File

@ -49,6 +49,11 @@
<body class="d-flex w-100 h-100 flex-column">
<main class="mb-auto">
<nav class="navbar navbar-expand-md navbar-light bg-light fixed-navbar shadow-sm">
<div class="container-fluid">
<div class="navbar-nav ml-auto">
{{ user.username }}#{{ user.discriminator }} <img src="{{ user.avatar_url }}" alt="avatar" style="width: 32px; height: 32px;" />
</div>
</div>
</nav>
{% block fullcontent %}
<div class="{% block containertype %}container{% endblock %} my-3">

View File

@ -5,5 +5,5 @@
{% block title %}{% trans "Index" %}{% endblock %}
{% block content %}
Historique de loup-garou
Votre rôle : {{ user.role }}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-success" type="submit">
</form>
{% endblock %}

View File

@ -10,4 +10,6 @@ urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
path('accounts/login/', LoginView.as_view()),
path('accounts/', include('django.contrib.auth.urls')),
path('lg/', include('lg.urls'))
]

View File

@ -1,5 +1,6 @@
authlib~=0.15
Django~=3.1
django-crispy-forms
django-crispy-forms~=1.9
django-polymorphic~=3.0
django-tables2~=2.3
gunicorn~=20.0

View File

@ -9,7 +9,9 @@ skipsdist = True
sitepackages = False
deps =
coverage
authlib~=0.15
Django~=3.1
django-crispy-forms~=1.9
django-polymorphic~=3.0
django-tables2~=2.3
commands =