1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-04 06:42:13 +02:00

Compare commits

...

16 Commits

Author SHA1 Message Date
f54dd30482 fix logout test 2025-07-03 15:18:29 +02:00
7eafe33945 Merge branch 'main' into django-5.2 2025-07-03 14:24:58 +02:00
6edef619aa change requirements.txt 2025-07-03 11:37:07 +02:00
8a1f30ebe2 Merge branch 'wei' into 'main'
Wei

See merge request bde/nk20!325
2025-07-01 18:14:45 +02:00
1567bc6ce5 Merge branch 'oidc' into 'main'
Oidc

See merge request bde/nk20!324
2025-06-27 22:29:51 +02:00
c411197af3 multiline support for RSA key in env 2025-06-27 22:13:43 +02:00
cdc6f0a3f8 Fix jwks.json 2025-06-27 12:13:54 +02:00
c153d5f10a Merge branch 'wei' into 'main'
Wei

See merge request bde/nk20!323
2025-06-26 17:08:27 +02:00
763535bea4 Merge branch 'oidc' into 'main'
OIDC 0 Quark 1

See merge request bde/nk20!322
2025-06-17 16:02:40 +02:00
df0d886db9 linters 2025-06-17 11:46:33 +02:00
092cc37320 OIDC 0 Quark 1 2025-06-17 00:38:11 +02:00
16b55e23af Merge branch 'thomasl-main-patch-84944' into 'main'
Update doc about scripts

See merge request bde/nk20!321
2025-06-14 20:24:49 +02:00
97621e8704 Update doc about scripts 2025-06-14 20:07:29 +02:00
cf4c23d1ac Merge branch 'oidc' into 'main'
oidc

See merge request bde/nk20!320
2025-06-14 18:36:24 +02:00
d71105976f oidc 2025-06-14 18:01:42 +02:00
89cc03141b allow search with club name 2025-06-12 18:48:29 +02:00
12 changed files with 75 additions and 38 deletions

View File

@ -21,3 +21,6 @@ EMAIL_PASSWORD=CHANGE_ME
# Wiki configuration # Wiki configuration
WIKI_USER=NoteKfet2020 WIKI_USER=NoteKfet2020
WIKI_PASSWORD= WIKI_PASSWORD=
# OIDC
OIDC_RSA_PRIVATE_KEY=CHANGE_ME

View File

@ -61,8 +61,8 @@ Bien que cela permette de créer une instance sur toutes les distributions,
6. (Optionnel) **Création d'une clé privée OpenID Connect** 6. (Optionnel) **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et copier la clé dans .env dans le champ
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). `OIDC_RSA_PRIVATE_KEY`.
7. Enjoy : 7. Enjoy :
@ -237,8 +237,8 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
7. **Création d'une clé privée OpenID Connect** 7. **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner le champ
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). `OIDC_RSA_PRIVATE_KEY` dans le .env (par défaut `/var/secrets/oidc.key`).
8. *Enjoy \o/* 8. *Enjoy \o/*

View File

@ -63,7 +63,8 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
valid_regex = is_regex(pattern) valid_regex = is_regex(pattern)
suffix = '__iregex' if valid_regex else '__istartswith' suffix = '__iregex' if valid_regex else '__istartswith'
prefix = '^' if valid_regex else '' prefix = '^' if valid_regex else ''
qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})) qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})
| Q(**{f'owner__name{suffix}': prefix + pattern}))
else: else:
qs = qs.none() qs = qs.none()
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self): def test_logout(self):
response = self.client.get(reverse("logout")) response = self.client.post(reverse("logout"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_admin_index(self): def test_admin_index(self):

View File

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet) router.register(path + '/consumer', ConsumerViewSet, basename='alias2')
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -1,8 +1,10 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes from oauth2_provider.scopes import BaseScopes
from member.models import Club from member.models import Club
from note.models import Alias
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from .backends import PermissionBackend from .backends import PermissionBackend
@ -17,25 +19,46 @@ class PermissionScopes(BaseScopes):
""" """
def get_all_scopes(self): def get_all_scopes(self):
return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})" scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
for p in Permission.objects.all() for club in Club.objects.all()} for p in Permission.objects.all() for club in Club.objects.all()}
scopes['openid'] = "OpenID Connect"
return scopes
def get_available_scopes(self, application=None, request=None, *args, **kwargs): def get_available_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
return [f"{p.id}_{p.membership.club.id}" scopes = [f"{p.id}_{p.membership.club.id}"
for t in Permission.PERMISSION_TYPES for t in Permission.PERMISSION_TYPES
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
scopes.append('openid')
return scopes
def get_default_scopes(self, application=None, request=None, *args, **kwargs): def get_default_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
return [f"{p.id}_{p.membership.club.id}" scopes = [f"{p.id}_{p.membership.club.id}"
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
scopes.append('openid')
return scopes
class PermissionOAuth2Validator(OAuth2Validator): class PermissionOAuth2Validator(OAuth2Validator):
oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0 oidc_claim_scope = OAuth2Validator.oidc_claim_scope
oidc_claim_scope.update({"name": 'openid',
"normalized_name": 'openid',
"email": 'openid',
})
def get_additional_claims(self, request):
return {
"name": request.user.username,
"normalized_name": Alias.normalize(request.user.username),
"email": request.user.email,
}
def get_discovery_claims(self, request):
claims = super().get_discovery_claims(self)
return claims + ["name", "normalized_name", "email"]
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
""" """
@ -54,6 +77,8 @@ class PermissionOAuth2Validator(OAuth2Validator):
if scope in scopes: if scope in scopes:
valid_scopes.add(scope) valid_scopes.add(scope)
request.scopes = valid_scopes if 'openid' in scopes:
valid_scopes.add('openid')
request.scopes = valid_scopes
return valid_scopes return valid_scopes

View File

@ -19,6 +19,7 @@ EXCLUDED = [
'oauth2_provider.accesstoken', 'oauth2_provider.accesstoken',
'oauth2_provider.grant', 'oauth2_provider.grant',
'oauth2_provider.refreshtoken', 'oauth2_provider.refreshtoken',
'oauth2_provider.idtoken',
'sessions.session', 'sessions.session',
] ]

View File

@ -171,7 +171,7 @@ class ScopesView(LoginRequiredMixin, TemplateView):
available_scopes = scopes.get_available_scopes(app) available_scopes = scopes.get_available_scopes(app)
context["scopes"][app] = OrderedDict() context["scopes"][app] = OrderedDict()
items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes] items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes]
items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0]))) # items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0])))
for k, v in items: for k, v in items:
context["scopes"][app][k] = v context["scopes"][app][k] = v

View File

@ -136,7 +136,7 @@ de diffusion utiles.
Faîtes attention, donc où la sortie est stockée. Faîtes attention, donc où la sortie est stockée.
Il prend 2 options : Il prend 4 options :
* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``, * ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``,
``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es
@ -149,7 +149,10 @@ Il prend 2 options :
pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe
laquelle des ``n+1`` dernières années. laquelle des ``n+1`` dernières années.
Le script sort sur la sortie standard la liste des adresses mails à inscrire. * ``--email``, qui prend en argument une chaine de caractère contenant une adresse email.
Si aucun email n'est renseigné, le script sort sur la sortie standard la liste des adresses mails à inscrire.
Dans le cas contraire, la liste est envoyée à l'adresse passée en argument.
Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est
malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives. malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives.

View File

@ -39,6 +39,7 @@ SECURE_HSTS_PRELOAD = True
INSTALLED_APPS = [ INSTALLED_APPS = [
# External apps # External apps
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'cas_server',
'colorfield', 'colorfield',
'crispy_bootstrap4', 'crispy_bootstrap4',
'crispy_forms', 'crispy_forms',
@ -270,7 +271,7 @@ OAUTH2_PROVIDER = {
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
'OIDC_ENABLED': True, 'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY': 'OIDC_RSA_PRIVATE_KEY':
os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'), os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines
'SCOPES': { 'openid': "OpenID Connect scope" }, 'SCOPES': { 'openid': "OpenID Connect scope" },
} }

View File

@ -138,9 +138,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}"> <a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
<i class="fa fa-user"></i> {% trans "My account" %} <i class="fa fa-user"></i> {% trans "My account" %}
</a> </a>
<a class="dropdown-item" href="{% url 'logout' %}"> <form method="post" action="{% url 'logout' %}">
<i class="fa fa-sign-out"></i> {% trans "Log out" %} {% csrf_token %}
</a> <button class="dropdown-item" type=submit">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</button>
</form>
</div> </div>
</li> </li>
{% else %} {% else %}

View File

@ -1,20 +1,20 @@
beautifulsoup4~=4.12.3 beautifulsoup4~=4.13.4
crispy-bootstrap4~=2023.1 crispy-bootstrap4~=2025.6
Django~=4.2.9 Django~=5.2.4
django-bootstrap-datepicker-plus~=5.0.5 django-bootstrap-datepicker-plus~=5.0.5
#django-cas-server~=2.0.0 django-cas-server~=3.1.0
django-colorfield~=0.11.0 django-colorfield~=0.14.0
django-crispy-forms~=2.1.0 django-crispy-forms~=2.4.0
django-extensions>=3.2.3 django-extensions>=4.1.0
django-filter~=23.5 django-filter~=25.1
#django-htcpcp-tea~=0.8.1 #django-htcpcp-tea~=0.8.1
django-mailer~=2.3.1 django-mailer~=2.3.2
django-oauth-toolkit~=2.3.0 django-oauth-toolkit~=3.0.1
django-phonenumber-field~=7.3.0 django-phonenumber-field~=8.1.0
django-polymorphic~=3.1.0 django-polymorphic~=3.1.0
djangorestframework~=3.14.0 djangorestframework~=3.16.0
django-rest-polymorphic~=0.1.10 django-rest-polymorphic~=0.1.10
django-tables2~=2.7.0 django-tables2~=2.7.5
python-memcached~=1.62 python-memcached~=1.62
phonenumbers~=8.13.28 phonenumbers~=9.0.8
Pillow>=10.2.0 Pillow>=11.3.0