mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-07 15:58:31 +02:00
Compare commits
26 Commits
be168c5ada
...
faster_ci
Author | SHA1 | Date | |
---|---|---|---|
3eed93e346 | |||
4da523a1ba | |||
e74ff54468 | |||
2e49c9ffbd | |||
d20a1038a8 | |||
f6b711bb1b | |||
893d87a9e1 | |||
9f3323c73e | |||
c57f81b920 | |||
0636d84286 | |||
ed06901fae | |||
28932f316b | |||
9b50ba722c | |||
3e3e61d23f | |||
1129815ca3 | |||
c13172d3ff | |||
fcc4121225 | |||
a06f355559 | |||
08df5fcccd | |||
b6c0f9758d | |||
a23093851f | |||
d803ab5ec2 | |||
d7a537b6b5 | |||
0941ee954d | |||
fd11d96d95 | |||
4bfc057454 |
3
.ansible-lint
Normal file
3
.ansible-lint
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
skip_list:
|
||||||
|
- command-instead-of-shell # Use shell only when shell functionality is required
|
||||||
|
- experimental # all rules tagged as experimental
|
@ -10,50 +10,22 @@ variables:
|
|||||||
# Debian Buster
|
# Debian Buster
|
||||||
py37-django22:
|
py37-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:buster-backports
|
image: otthorn/nk20_ci_37
|
||||||
before_script:
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -t buster-backports -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py37-django22
|
script: tox -e py37-django22
|
||||||
|
|
||||||
# Ubuntu 20.04
|
# Ubuntu 20.04
|
||||||
py38-django22:
|
py38-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: ubuntu:20.04
|
image: otthorn/nk20_ci_38
|
||||||
before_script:
|
|
||||||
# Fix tzdata prompt
|
|
||||||
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py38-django22
|
script: tox -e py38-django22
|
||||||
|
|
||||||
# Debian Bullseye
|
# Debian Bullseye
|
||||||
py39-django22:
|
py39-django22:
|
||||||
stage: test
|
stage: test
|
||||||
image: debian:bullseye
|
image: otthorn/nk20_ci_39
|
||||||
before_script:
|
|
||||||
- >
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install --no-install-recommends -y
|
|
||||||
python3-django python3-django-crispy-forms
|
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
|
||||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
|
||||||
script: tox -e py39-django22
|
script: tox -e py39-django22
|
||||||
|
|
||||||
|
# Tox linter
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: debian:buster-backports
|
image: debian:buster-backports
|
||||||
@ -64,6 +36,20 @@ linters:
|
|||||||
# Be nice to new contributors, but please use `tox`
|
# Be nice to new contributors, but please use `tox`
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
||||||
|
# Ansible linter
|
||||||
|
ansible-linter:
|
||||||
|
stage: quality-assurance
|
||||||
|
image: otthorn/nk20_ci_ansiblelint
|
||||||
|
script: ansible-lint ansible/
|
||||||
|
|
||||||
|
# Docker linter
|
||||||
|
docker-linter:
|
||||||
|
stage: quality-assurance
|
||||||
|
image: hadolint/hadolint
|
||||||
|
script:
|
||||||
|
- hadolint -c .hadolint Dockerfile
|
||||||
|
- hadolint -c .hadolint docker_ci/Dockerfile.*
|
||||||
|
|
||||||
# Compile documentation
|
# Compile documentation
|
||||||
documentation:
|
documentation:
|
||||||
stage: docs
|
stage: docs
|
||||||
|
4
.hadolint
Normal file
4
.hadolint
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ignored:
|
||||||
|
- DL3008 # Do not force to pin version in apt (Debian)
|
||||||
|
- DL3013 # Do not force to pin version in pip (PyPI)
|
||||||
|
- DL3018 # Do not force to pin version in apk (Alpine)
|
@ -4,14 +4,10 @@
|
|||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils import timezone
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework import serializers
|
|
||||||
from member.api.serializers import ProfileSerializer, MembershipSerializer
|
|
||||||
from note.api.serializers import NoteSerializer
|
|
||||||
from note.models import Alias
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for Users.
|
REST API Serializer for Users.
|
||||||
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||||
@ -26,7 +22,7 @@ class UserSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeSerializer(serializers.ModelSerializer):
|
class ContentTypeSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
REST API Serializer for Users.
|
REST API Serializer for Users.
|
||||||
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
|
||||||
@ -35,42 +31,3 @@ class ContentTypeSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ContentType
|
model = ContentType
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class OAuthSerializer(serializers.ModelSerializer):
|
|
||||||
"""
|
|
||||||
Informations that are transmitted by OAuth.
|
|
||||||
For now, this includes user, profile and valid memberships.
|
|
||||||
This should be better managed later.
|
|
||||||
"""
|
|
||||||
normalized_name = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
profile = ProfileSerializer()
|
|
||||||
|
|
||||||
note = NoteSerializer()
|
|
||||||
|
|
||||||
memberships = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
def get_normalized_name(self, obj):
|
|
||||||
return Alias.normalize(obj.username)
|
|
||||||
|
|
||||||
def get_memberships(self, obj):
|
|
||||||
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
|
|
||||||
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now()))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = (
|
|
||||||
'id',
|
|
||||||
'username',
|
|
||||||
'normalized_name',
|
|
||||||
'first_name',
|
|
||||||
'last_name',
|
|
||||||
'email',
|
|
||||||
'is_superuser',
|
|
||||||
'is_active',
|
|
||||||
'is_staff',
|
|
||||||
'profile',
|
|
||||||
'note',
|
|
||||||
'memberships',
|
|
||||||
)
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from decimal import Decimal
|
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
@ -153,8 +152,6 @@ class TestAPI(TestCase):
|
|||||||
value = value.isoformat()
|
value = value.isoformat()
|
||||||
elif isinstance(value, ImageFieldFile):
|
elif isinstance(value, ImageFieldFile):
|
||||||
value = value.name
|
value = value.name
|
||||||
elif isinstance(value, Decimal):
|
|
||||||
value = str(value)
|
|
||||||
query = json.dumps({field.name: value})
|
query = json.dumps({field.name: value})
|
||||||
|
|
||||||
# Create sample permission
|
# Create sample permission
|
||||||
|
@ -5,7 +5,6 @@ from django.conf import settings
|
|||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from .views import UserInformationView
|
|
||||||
from .viewsets import ContentTypeViewSet, UserViewSet
|
from .viewsets import ContentTypeViewSet, UserViewSet
|
||||||
|
|
||||||
# Routers provide an easy way of automatically determining the URL conf.
|
# Routers provide an easy way of automatically determining the URL conf.
|
||||||
@ -48,6 +47,5 @@ app_name = 'api'
|
|||||||
# Additionally, we include login URLs for the browsable API.
|
# Additionally, we include login URLs for the browsable API.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^', include(router.urls)),
|
url('^', include(router.urls)),
|
||||||
url('^me/', UserInformationView.as_view()),
|
|
||||||
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
]
|
]
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from rest_framework.generics import RetrieveAPIView
|
|
||||||
|
|
||||||
from .serializers import OAuthSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class UserInformationView(RetrieveAPIView):
|
|
||||||
"""
|
|
||||||
These fields are give to OAuth authenticators.
|
|
||||||
"""
|
|
||||||
serializer_class = OAuthSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return User.objects.filter(pk=self.request.user.pk)
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return self.request.user
|
|
@ -1,17 +0,0 @@
|
|||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from cas_server.auth import DjangoAuthUser # pragma: no cover
|
|
||||||
from note.models import Alias
|
|
||||||
|
|
||||||
|
|
||||||
class CustomAuthUser(DjangoAuthUser): # pragma: no cover
|
|
||||||
"""
|
|
||||||
Override Django Auth User model to define a custom Matrix username.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def attributs(self):
|
|
||||||
d = super().attributs()
|
|
||||||
if self.user:
|
|
||||||
d["normalized_name"] = Alias.normalize(self.user.username)
|
|
||||||
return d
|
|
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 2.2.19 on 2021-03-13 11:35
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('member', '0006_create_note_account_bde_membership'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='membership',
|
|
||||||
name='roles',
|
|
||||||
field=models.ManyToManyField(related_name='memberships', to='permission.Role', verbose_name='roles'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='promotion',
|
|
||||||
field=models.PositiveSmallIntegerField(default=2021, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 2.2.19 on 2021-03-13 11:35
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('note', '0004_remove_null_tag_on_charfields'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='alias',
|
|
||||||
name='note',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.Note'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -134,6 +134,8 @@ class PermissionBackend(ModelBackend):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
sess = get_current_session()
|
sess = get_current_session()
|
||||||
|
if sess is not None and sess.session_key is None:
|
||||||
|
return False
|
||||||
|
|
||||||
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
||||||
return True
|
return True
|
||||||
|
@ -3511,7 +3511,6 @@
|
|||||||
56,
|
56,
|
||||||
57,
|
57,
|
||||||
58,
|
58,
|
||||||
135,
|
|
||||||
137,
|
137,
|
||||||
143,
|
143,
|
||||||
147,
|
147,
|
||||||
@ -3522,7 +3521,8 @@
|
|||||||
176,
|
176,
|
||||||
177,
|
177,
|
||||||
180,
|
180,
|
||||||
181
|
181,
|
||||||
|
182
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 2.2.19 on 2021-03-21 09:34
|
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('treasury', '0002_invoice_remove_png_extension'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='quantity',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=7, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='specialtransactionproxy',
|
|
||||||
name='remittance',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_proxies', to='treasury.Remittance', verbose_name='Remittance'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -5,7 +5,6 @@ from datetime import date
|
|||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MinValueValidator
|
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@ -132,15 +131,12 @@ class Product(models.Model):
|
|||||||
verbose_name=_("Designation"),
|
verbose_name=_("Designation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = models.DecimalField(
|
quantity = models.PositiveIntegerField(
|
||||||
decimal_places=2,
|
verbose_name=_("Quantity")
|
||||||
max_digits=7,
|
|
||||||
verbose_name=_("Quantity"),
|
|
||||||
validators=[MinValueValidator(0)],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
amount = models.IntegerField(
|
amount = models.IntegerField(
|
||||||
verbose_name=_("Unit price"),
|
verbose_name=_("Unit price")
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% load escape_tex i18n %}
|
{% load escape_tex %}
|
||||||
{% language fr %}
|
|
||||||
\documentclass[a4paper,11pt]{article}
|
\documentclass[a4paper,11pt]{article}
|
||||||
|
|
||||||
\usepackage{fontspec}
|
\usepackage{fontspec}
|
||||||
@ -177,4 +176,3 @@ TVA non applicable, article 293 B du CGI.
|
|||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
||||||
{% endlanguage %}
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 2.2.19 on 2021-03-13 11:35
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wei', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='weiclub',
|
|
||||||
name='year',
|
|
||||||
field=models.PositiveIntegerField(default=2021, unique=True, verbose_name='year'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='weiregistration',
|
|
||||||
name='information_json',
|
|
||||||
field=models.TextField(default='{}', help_text='Information about the registration (buses for old members, survey for the new members), encoded in JSON', verbose_name='registration information'),
|
|
||||||
),
|
|
||||||
]
|
|
18
docker_ci/Dockerfile.37
Normal file
18
docker_ci/Dockerfile.37
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM debian:buster-backports
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Debian Buster backports image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -t buster-backports -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
22
docker_ci/Dockerfile.38
Normal file
22
docker_ci/Dockerfile.38
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Ubuntu 20.04 image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
# fix tzdata prompt
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
18
docker_ci/Dockerfile.39
Normal file
18
docker_ci/Dockerfile.39
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM debian:bullseye
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Debian Bulleye image with django and tox \
|
||||||
|
installed for testing purposes"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
python3-django python3-django-crispy-forms \
|
||||||
|
python3-django-extensions python3-django-filters \
|
||||||
|
python3-django-polymorphic \
|
||||||
|
python3-djangorestframework python3-django-oauth-toolkit \
|
||||||
|
python3-psycopg2 python3-pil \
|
||||||
|
python3-babel python3-lockfile python3-pip python3-phonenumbers \
|
||||||
|
python3-memcache \
|
||||||
|
python3-bs4 python3-setuptools tox texlive-xetex \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
10
docker_ci/Dockerfile.ansiblelint
Normal file
10
docker_ci/Dockerfile.ansiblelint
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.9-alpine
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Alpine image with ansible-lint and yamllint \
|
||||||
|
installed for linting purposes"
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev cargo
|
||||||
|
RUN pip install --no-cache-dir "yamllint>=1.26.0,<2.0"
|
||||||
|
RUN pip install --no-cache-dir "ansible-lint==5.0.0"
|
||||||
|
RUN pip install --no-cache-dir "ansible>=2.10,<2.11"
|
8
docker_ci/Dockerfile.tox
Normal file
8
docker_ci/Dockerfile.tox
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM alpine:3.13
|
||||||
|
|
||||||
|
LABEL maintainer="otthorn@crans.org"
|
||||||
|
LABEL description="Alpine image with tox \
|
||||||
|
installed for linting purposes"
|
||||||
|
|
||||||
|
RUN apk --no-cache add py3-pip=20.3.4-r0
|
||||||
|
RUN pip install --no-cache-dir tox==3.22.0
|
21
docker_ci/README.md
Normal file
21
docker_ci/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Docker CI
|
||||||
|
|
||||||
|
Ce dossier contient les images docker à construire pour la CI. L'idée est
|
||||||
|
d'avoir une image pré-construire, au dessus laquel il y a besoin de faire
|
||||||
|
tourner uniquement les commandes qui nous intéresse. Cela permet notamment de
|
||||||
|
réduire drastiquement le temps que nécessite chaque test car seul la dernière
|
||||||
|
couche (layer) de l'image a besoin d'etre éxécuter.
|
||||||
|
|
||||||
|
## Build les images
|
||||||
|
|
||||||
|
Pour build les images il suffit de lancer les commandes suivantes
|
||||||
|
|
||||||
|
```
|
||||||
|
cd docker_ci/
|
||||||
|
docker build -t nk20_ci_37 -f Dockerfile.37 .
|
||||||
|
docker build -t nk20_ci_38 -f Dockerfile.38 .
|
||||||
|
docker build -t nk20_ci_39 -f Dockerfile.39 .
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles sont acutellement build et disponible sur dockerhub
|
||||||
|
https://hub.docker.com/otthorn/nk20_ci_37
|
@ -1,80 +0,0 @@
|
|||||||
Service d'Authentification Centralisé (CAS)
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
Un `CAS <https://fr.wikipedia.org/wiki/Central_Authentication_Service>`_ est
|
|
||||||
déployé sur la Note Kfet. Il est accessible à l'adresse `<https://note.crans.org/cas/>`_.
|
|
||||||
Il a pour but uniquement d'authentifier les utilisateurs via la note et ne communique
|
|
||||||
que peu d'informations.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Le serveur CAS utilisé est implémenté grâce au paquet ``django-cas-server``. Il peut être
|
|
||||||
installé soit par PIP soit sur une machine Debian via
|
|
||||||
``apt install python3-django-cas-server``.
|
|
||||||
|
|
||||||
On ajoute ensuite ``cas_server`` aux applications Django installées. On n'oublie pas ni
|
|
||||||
d'appliquer les migrations (``./manage.py migrate``) ni de collecter les fichiers
|
|
||||||
statiques (``./manage.py collectstatic``).
|
|
||||||
|
|
||||||
On enregistre les routes dans ``note_kfet/urls.py`` :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
urlpatterns.append(
|
|
||||||
path('cas/', include('cas_server.urls', namespace='cas_server'))
|
|
||||||
)
|
|
||||||
|
|
||||||
Le CAS est désormais déjà prêt à être utilisé. Toutefois, puisque l'on utilise un site
|
|
||||||
Django-admin personnalisé, on n'oublie pas d'enregistrer les pages d'administration :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
if "cas_server" in settings.INSTALLED_APPS:
|
|
||||||
from cas_server.admin import *
|
|
||||||
from cas_server.models import *
|
|
||||||
admin_site.register(ServicePattern, ServicePatternAdmin)
|
|
||||||
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
|
|
||||||
|
|
||||||
Enfin, on souhaite pouvoir fournir au besoin le pseudo normalisé. Pour cela, on crée une
|
|
||||||
classe dans ``member.auth`` :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
class CustomAuthUser(DjangoAuthUser):
|
|
||||||
def attributs(self):
|
|
||||||
d = super().attributs()
|
|
||||||
if self.user:
|
|
||||||
d["normalized_name"] = Alias.normalize(self.user.username)
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
Puis on source ce fichier dans les paramètres :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
|
|
||||||
|
|
||||||
Utilisation
|
|
||||||
-----------
|
|
||||||
Le service est accessible sur `<https://note.crans.org/cas/>`_. C'est ce lien qu'il faut
|
|
||||||
donner à votre application.
|
|
||||||
|
|
||||||
L'application doit néanmoins être autorisée à accéder au CAS. Pour cela, rendez-vous
|
|
||||||
dans Django-admin (`<https://note.crans.org/cas/admin/>`_), dans
|
|
||||||
``Service Central d'Authentification/Motifs de services``, ajoutez une nouvelle entrée.
|
|
||||||
Choisissez votre position favorite puis le nom de l'application.
|
|
||||||
|
|
||||||
Les champs importants sont les deux suivants :
|
|
||||||
|
|
||||||
* **Motif :** il s'agit d'une expression régulière qui doit reconnaitre le site voulu.
|
|
||||||
Par exemple, pour autoriser Belenios (`<https://belenios.crans.org>`_), on rentrera
|
|
||||||
le motif ``^https?://belenios\.crans\.org/.*$``.
|
|
||||||
* **Champ d'utilisateur :** C'est le pseudo que renverra le CAS. Par défaut, il s'agira
|
|
||||||
du nom de note principal, mais il arrive parfois que certains sites supportent mal
|
|
||||||
d'avoir des caractères UTF-8 dans le pseudo. C'est par exemple le cas de Belenios.
|
|
||||||
On rentrera alors ``normalized_name`` dans ce champ, qui correspond à la version
|
|
||||||
normalisée (sans accent ni espace ni aucun caractère non-ASCII) du pseudo, et qui
|
|
||||||
suffit à identifier une personne.
|
|
||||||
|
|
||||||
On peut également utiliser le ``Single log out`` si besoin.
|
|
@ -1,28 +0,0 @@
|
|||||||
Applications externes
|
|
||||||
=====================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Applications externes
|
|
||||||
|
|
||||||
cas
|
|
||||||
oauth2
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
L'utilisation de la note par des services externes est actuellement en beta. Il est
|
|
||||||
fort à parier que cette utilisation sera revue et améliorée à l'avenir.
|
|
||||||
|
|
||||||
Puisque la Note Kfet recense tous les comptes des adhérents BDE, les clubs ont alors
|
|
||||||
la possibilité de développer leurs propres applications et de les interfacer avec la
|
|
||||||
note. De cette façon, chaque application peut authentifier ses utilisateurs via la note,
|
|
||||||
et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts
|
|
||||||
via l'API.
|
|
||||||
|
|
||||||
Deux protocoles d'authentification sont implémentées :
|
|
||||||
|
|
||||||
* `CAS <cas>`_
|
|
||||||
* `OAuth2 <oauth2>`_
|
|
||||||
|
|
||||||
À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce
|
|
||||||
mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent
|
|
||||||
ces protocoles pour récupérer leurs adhérents.
|
|
@ -1,133 +0,0 @@
|
|||||||
OAuth2
|
|
||||||
======
|
|
||||||
|
|
||||||
L'authentification `OAuth2 <https://fr.wikipedia.org/wiki/OAuth>`_ est supportée par la
|
|
||||||
Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateurs, mais aussi
|
|
||||||
de transmettre des informations à un service tiers tels que des informations personnelles,
|
|
||||||
le solde de la note ou encore les adhésions de l'utilisateur, en l'avertissant sur
|
|
||||||
quelles données sont effectivement collectées.
|
|
||||||
|
|
||||||
.. danger::
|
|
||||||
L'implémentation actuelle ne permet pas de choisir quels droits on offre. Se connecter
|
|
||||||
par OAuth2 offre actuellement exactement les mêmes permissions que l'on n'aurait
|
|
||||||
normalement, avec le masque le plus haut, y compris en écriture.
|
|
||||||
|
|
||||||
Faites alors très attention lorsque vous vous connectez à un service tiers via OAuth2,
|
|
||||||
et contrôlez bien exactement ce que l'application fait de vos données, à savoir si
|
|
||||||
elle ignore bien tout ce dont elle n'a pas besoin.
|
|
||||||
|
|
||||||
À l'avenir, la fenêtre d'authentification pourra vous indiquer clairement quels
|
|
||||||
paramètres sont collectés.
|
|
||||||
|
|
||||||
Configuration du serveur
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
On utilise ``django-oauth-toolkit``, qui peut être installé grâce à PIP ou bien via APT,
|
|
||||||
via le paquet ``python3-django-oauth-toolkit``.
|
|
||||||
|
|
||||||
On commence par ajouter ``oauth2_provider`` aux applications Django installées. On
|
|
||||||
n'oublie pas ni d'appliquer les migrations (``./manage.py migrate``) ni de collecter
|
|
||||||
les fichiers statiques (``./manage.py collectstatic``).
|
|
||||||
|
|
||||||
On souhaite que l'API gérée par ``django-rest-framework`` puisse être accessible via
|
|
||||||
l'authentification OAuth2. On adapte alors la configuration pour permettre cela :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
|
||||||
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
|
||||||
...
|
|
||||||
],
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
On ajoute les routes dans ``urls.py`` :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
urlpatterns.append(
|
|
||||||
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
|
|
||||||
)
|
|
||||||
|
|
||||||
L'OAuth2 est désormais prêt à être utilisé.
|
|
||||||
|
|
||||||
|
|
||||||
Configuration client
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Contrairement au `CAS <cas>`_, n'importe qui peut en théorie créer une application OAuth2.
|
|
||||||
En théorie, car pour l'instant les permissions ne leur permettent pas.
|
|
||||||
|
|
||||||
Pour créer une application, il faut se rendre à la page
|
|
||||||
`/o/applications/ <https://note.crans.org/o/applications/>`_. Dans ``client type``,
|
|
||||||
rentrez ``public`` (ou ``confidential`` selon vos choix), et vous rentrerez
|
|
||||||
généralement ``authorization-code`` dans ``Authorization Grant Type``.
|
|
||||||
Le champ ``Redirect Uris`` contient une liste d'adresses URL autorisées pour des
|
|
||||||
redirections post-connexion.
|
|
||||||
|
|
||||||
Il vous suffit de donner à votre application :
|
|
||||||
|
|
||||||
* L'identifiant client (client-ID)
|
|
||||||
* La clé secrète
|
|
||||||
* Les scopes : sous-ensemble de ``[read, write]`` (ignoré pour l'instant, cf premier paragraphe)
|
|
||||||
* L'URL d'autorisation : `<https://note.crans.org/o/authorize/>`_
|
|
||||||
* L'URL d'obtention de jeton : `<https://note.crans.org/o/token/>`_
|
|
||||||
* L'URL de récupération des informations de l'utilisateur : `<https://note.crans.org/api/me/>`_
|
|
||||||
|
|
||||||
N'hésitez pas à consulter la page `<https://note.crans.org/api/me/>`_ pour s'imprégner
|
|
||||||
du format renvoyé.
|
|
||||||
|
|
||||||
Avec Django-allauth
|
|
||||||
###################
|
|
||||||
|
|
||||||
Si vous utilisez Django-allauth pour votre propre application, vous pouvez utiliser
|
|
||||||
le module pré-configuré disponible ici :
|
|
||||||
`<https://gitlab.crans.org/bde/allauth-note-kfet>`_. Pour l'installer, vous
|
|
||||||
pouvez simplement faire :
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
$ pip3 install git+https://gitlab.crans.org/bde/allauth-note-kfet.git
|
|
||||||
|
|
||||||
L'installation du module se fera automatiquement.
|
|
||||||
|
|
||||||
Il vous suffit ensuite d'inclure l'application ``allauth_note_kfet`` à vos applications
|
|
||||||
installées (sur votre propre client), puis de bien ajouter l'application sociale :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
SOCIALACCOUNT_PROVIDERS = {
|
|
||||||
'notekfet': {
|
|
||||||
# 'DOMAIN': 'note.crans.org',
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il
|
|
||||||
se connectera à ``note.crans.org`` si vous ne renseignez rien.
|
|
||||||
|
|
||||||
En créant l'application sur la note, vous pouvez renseigner
|
|
||||||
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
|
|
||||||
à adapter selon votre configuration.
|
|
||||||
|
|
||||||
Vous devrez ensuite enregistrer l'application sociale dans la base de données.
|
|
||||||
Vous pouvez passer par Django-admin, mais cela peut nécessiter d'avoir déjà un compte,
|
|
||||||
alors autant le faire via un shell python :
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from allauth.socialaccount.models import SocialApp
|
|
||||||
SocialApp.objects.create(
|
|
||||||
name="Note Kfet",
|
|
||||||
provider="notekfet",
|
|
||||||
client_id="VOTRECLIENTID",
|
|
||||||
secret="VOTRESECRET",
|
|
||||||
key="",
|
|
||||||
)
|
|
||||||
|
|
||||||
Si vous avez bien configuré ``django-allauth``, vous êtes désormais prêts par à vous
|
|
||||||
connecter via la note :) Par défaut, nom, prénom, pseudo et adresse e-mail sont
|
|
||||||
récupérés. Les autres données sont stockées mais inutilisées.
|
|
@ -12,4 +12,3 @@ Des informations complémentaires sont également disponibles sur le `Wiki Crans
|
|||||||
:caption: Développement de la NK20
|
:caption: Développement de la NK20
|
||||||
|
|
||||||
apps/index
|
apps/index
|
||||||
external_services/index
|
|
||||||
|
@ -52,9 +52,3 @@ if "rest_framework" in settings.INSTALLED_APPS:
|
|||||||
from rest_framework.authtoken.admin import *
|
from rest_framework.authtoken.admin import *
|
||||||
from rest_framework.authtoken.models import *
|
from rest_framework.authtoken.models import *
|
||||||
admin_site.register(Token, TokenAdmin)
|
admin_site.register(Token, TokenAdmin)
|
||||||
|
|
||||||
if "cas_server" in settings.INSTALLED_APPS:
|
|
||||||
from cas_server.admin import *
|
|
||||||
from cas_server.models import *
|
|
||||||
admin_site.register(ServicePattern, ServicePatternAdmin)
|
|
||||||
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
|
|
||||||
|
@ -12,7 +12,7 @@ def read_env():
|
|||||||
directory.
|
directory.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(BASE_DIR, '.env')) as f:
|
with open('.env') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
content = ''
|
content = ''
|
||||||
@ -30,7 +30,6 @@ def read_env():
|
|||||||
|
|
||||||
|
|
||||||
# Try to load environment variables from project .env
|
# Try to load environment variables from project .env
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
read_env()
|
read_env()
|
||||||
|
|
||||||
# Load base settings
|
# Load base settings
|
||||||
|
@ -239,7 +239,6 @@ REST_FRAMEWORK = {
|
|||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
|
||||||
],
|
],
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
'PAGE_SIZE': 20,
|
'PAGE_SIZE': 20,
|
||||||
@ -274,6 +273,3 @@ PIC_RATIO = 1
|
|||||||
# Custom phone number format
|
# Custom phone number format
|
||||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||||
|
|
||||||
# We add custom information to CAS, in order to give a normalized name to other services
|
|
||||||
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
|
|
||||||
|
@ -45,11 +45,6 @@ if "oauth2_provider" in settings.INSTALLED_APPS:
|
|||||||
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
|
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
|
||||||
)
|
)
|
||||||
|
|
||||||
if "cas_server" in settings.INSTALLED_APPS:
|
|
||||||
urlpatterns.append(
|
|
||||||
path('cas/', include('cas_server.urls', namespace='cas_server'))
|
|
||||||
)
|
|
||||||
|
|
||||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
beautifulsoup4~=4.7.1
|
beautifulsoup4~=4.7.1
|
||||||
Django~=2.2.15
|
Django~=2.2.15
|
||||||
django-bootstrap-datepicker-plus~=3.0.5
|
django-bootstrap-datepicker-plus~=3.0.5
|
||||||
django-cas-server~=1.2.0
|
|
||||||
django-colorfield~=0.3.2
|
django-colorfield~=0.3.2
|
||||||
django-crispy-forms~=1.7.2
|
django-crispy-forms~=1.7.2
|
||||||
django-extensions~=2.1.4
|
django-extensions~=2.1.4
|
||||||
|
Reference in New Issue
Block a user