From d773303d187acaa921bdd670f7be73174c8a248a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 19 Oct 2020 23:44:47 +0200 Subject: [PATCH 1/4] Add possibility to authenticate an account with its IP address --- note_kfet/middlewares.py | 39 ++++++++++++++++++++++++++++++++-- note_kfet/settings/__init__.py | 3 --- note_kfet/settings/base.py | 2 ++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/note_kfet/middlewares.py b/note_kfet/middlewares.py index 22f3e264..f545d839 100644 --- a/note_kfet/middlewares.py +++ b/note_kfet/middlewares.py @@ -2,12 +2,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.conf import settings +from django.contrib.auth import login from django.contrib.auth.models import AnonymousUser, User +from django.contrib.sessions.backends.db import SessionStore from threading import local -from django.contrib.sessions.backends.db import SessionStore - USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session') IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip') @@ -78,6 +78,41 @@ class SessionMiddleware(object): return response +class LoginByIPMiddleware(object): + """ + Allow some users to be authenticated based on their IP address. + For example, the "note" account should not be used elsewhere than the Kfet computer, + and should not have any password. + The password that is stored in database should be on the form "ipbased$my.public.ip.address". + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + """ + If the user is not authenticated, get the used IP address + and check if an user is authorized to be automatically logged with this address. + If it is the case, the logging is performed with the full rights. + """ + if not request.user.is_authenticated: + if 'HTTP_X_REAL_IP' in request.META: + ip = request.META.get('HTTP_X_REAL_IP') + elif 'HTTP_X_FORWARDED_FOR' in request.META: + ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0] + else: + ip = request.META.get('REMOTE_ADDR') + + qs = User.objects.filter(password=f"ipbased${ip}") + if qs.exists(): + login(request, qs.get()) + session = request.session + session["permission_mask"] = 42 + session.save() + + return self.get_response(request) + + class TurbolinksMiddleware(object): """ Send the `Turbolinks-Location` header in response to a visit that was redirected, diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py index 0c76b6f4..3d995367 100644 --- a/note_kfet/settings/__init__.py +++ b/note_kfet/settings/__init__.py @@ -49,9 +49,6 @@ try: except ImportError: pass -if "logs" in INSTALLED_APPS: - MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',) - if DEBUG: PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor'] if "debug_toolbar" in INSTALLED_APPS: diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index f94a68e0..1cbf6ed7 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -79,6 +79,8 @@ MIDDLEWARE = [ 'django.middleware.locale.LocaleMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware', + 'note_kfet.middlewares.SessionMiddleware', + 'note_kfet.middlewares.LoginByIPMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware', ] From 2097e67321d7c4cff69a71f456554120f5ea19cf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 20 Oct 2020 00:19:49 +0200 Subject: [PATCH 2/4] Add permissions to PC Kfet --- apps/permission/fixtures/initial.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 78891499..6faaa11f 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -2081,7 +2081,7 @@ ], "query": "{}", "type": "change", - "mask": 1, + "mask": 2, "field": "invalidity_reason", "permanent": false, "description": "Modifier la raison d'invalidité d'une transaction" @@ -3415,6 +3415,26 @@ ] } }, + { + "model": "permission.role", + "pk": 20, + "fields": { + "for_club": 2, + "name": "PC Kfet", + "permissions": [ + 6, + 24, + 25, + 26, + 27, + 30, + 150, + 166, + 167, + 168 + ] + } + }, { "model": "wei.weirole", "pk": 12, From 6cc3cf4174472d9198d55189a0b3c2c0ff80ec24 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 20 Oct 2020 00:28:49 +0200 Subject: [PATCH 3/4] A migration put the right role in the note account's memberships --- ...0006_create_note_account_bde_membership.py | 50 +++++++++++++++++++ apps/scripts | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 apps/member/migrations/0006_create_note_account_bde_membership.py diff --git a/apps/member/migrations/0006_create_note_account_bde_membership.py b/apps/member/migrations/0006_create_note_account_bde_membership.py new file mode 100644 index 00000000..a76ac922 --- /dev/null +++ b/apps/member/migrations/0006_create_note_account_bde_membership.py @@ -0,0 +1,50 @@ +import sys + +from django.db import migrations + + +def give_note_account_permissions(apps, schema_editor): + """ + Automatically manage the membership of the Note account. + """ + User = apps.get_model("auth", "user") + Membership = apps.get_model("member", "membership") + Role = apps.get_model("permission", "role") + + note = User.objects.filter(username="note") + if not note.exists(): + # We are in a test environment, don't log error message + if len(sys.argv) > 1 and sys.argv[1] == 'test': + return + print("Warning: Note account was not found. The note account was not imported.") + print("Make sure you have imported the NK15 database. The new import script handles correctly the permissions.") + print("This migration will be ignored, you can re-run it if you forgot the note account or ignore it if you " + "don't want this account.") + return + + note = note.get() + + # Set for the two clubs a large expiration date and the correct role. + for m in Membership.objects.filter(user_id=note.id).all(): + m.date_end = "3142-12-12" + m.roles.set(Role.objects.filter(name="PC Kfet").all()) + m.save() + # By default, the note account is only authorized to be logged from localhost. + note.password = "ipbased$127.0.0.1" + note.is_active = False + note.save() + # Ensure that the note of the account is disabled + note.note.inactivity_reason = 'forced' + note.note.is_active = False + note.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('member', '0005_remove_null_tag_on_charfields'), + ('permission', '0001_initial'), + ] + + operations = [ + migrations.RunPython(give_note_account_permissions), + ] diff --git a/apps/scripts b/apps/scripts index 7e27c3b7..84be9d00 160000 --- a/apps/scripts +++ b/apps/scripts @@ -1 +1 @@ -Subproject commit 7e27c3b71b04af0867d5fbe4916e2d1278637599 +Subproject commit 84be9d0062beee2c1dc09f2f7ef082e6f6240ad1 From 58aa4983e32a4a6bddd47261850ff10ac8803ce9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 20 Oct 2020 10:30:41 +0200 Subject: [PATCH 4/4] The note account must be active in order to have access to the Rest Framework API --- .../migrations/0006_create_note_account_bde_membership.py | 2 +- apps/scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/member/migrations/0006_create_note_account_bde_membership.py b/apps/member/migrations/0006_create_note_account_bde_membership.py index a76ac922..4a18301f 100644 --- a/apps/member/migrations/0006_create_note_account_bde_membership.py +++ b/apps/member/migrations/0006_create_note_account_bde_membership.py @@ -31,7 +31,7 @@ def give_note_account_permissions(apps, schema_editor): m.save() # By default, the note account is only authorized to be logged from localhost. note.password = "ipbased$127.0.0.1" - note.is_active = False + note.is_active = True note.save() # Ensure that the note of the account is disabled note.note.inactivity_reason = 'forced' diff --git a/apps/scripts b/apps/scripts index 84be9d00..654492f9 160000 --- a/apps/scripts +++ b/apps/scripts @@ -1 +1 @@ -Subproject commit 84be9d0062beee2c1dc09f2f7ef082e6f6240ad1 +Subproject commit 654492f9e9262c37fecb43261f02557aeb6e1cc1