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

Compare commits

..

1 Commits

Author SHA1 Message Date
1c5e951c2f Merge branch 'beta' into 'master'
Various fixes

See merge request bde/nk20!133
2020-10-07 12:06:48 +02:00
19 changed files with 57 additions and 287 deletions

View File

@ -1,50 +0,0 @@
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 = True
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),
]

View File

@ -43,24 +43,8 @@ class UserTable(tables.Table):
section = tables.Column(accessor='profile__section') section = tables.Column(accessor='profile__section')
# Override the column to let replace the URL
email = tables.EmailColumn(linkify=lambda record: "mailto:{}".format(record.email))
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance")) balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"))
def render_email(self, record, value):
# Replace the email by a dash if the user can't see the profile detail
# Replace also the URL
if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile):
value = ""
record.email = value
return value
def render_section(self, record, value):
return value \
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \
else ""
def render_balance(self, record, value): def render_balance(self, record, value):
return pretty_money(value)\ return pretty_money(value)\
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "" if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else ""

View File

@ -25,7 +25,6 @@
</a> </a>
</dd> </dd>
{% if "member.view_profile"|has_perm:user_object.profile %}
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.section }}</dd> <dd class="col-xl-6">{{ user_object.profile.section }}</dd>
@ -46,7 +45,6 @@
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd> <dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
{% endif %} {% endif %}
{% endif %}
</dl> </dl>
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load i18n perms %} {% load i18n perms %}
{% block content %} {% block content %}
{% if can_manage_registrations %} {% if "member.change_profile_registration_valid"|has_perm:user %}
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}"> <a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
<i class="fa fa-user-plus"></i> {% trans "Registrations" %} <i class="fa fa-user-plus"></i> {% trans "Registrations" %}
</a> </a>

View File

@ -70,7 +70,6 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
form.fields['email'].required = True form.fields['email'].required = True
form.fields['email'].help_text = _("This address must be valid.") form.fields['email'].help_text = _("This address must be valid.")
if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile):
context['profile_form'] = self.profile_form(instance=context['user_object'].profile, context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
data=self.request.POST if self.request.POST else None) data=self.request.POST if self.request.POST else None)
if not self.object.profile.report_frequency: if not self.object.profile.report_frequency:
@ -235,13 +234,6 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
return qs return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\
.filter(profile__registration_valid=False)
context["can_manage_registrations"] = pre_registered_users.exists()
return context
class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
""" """
@ -255,8 +247,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
note = context['object'].note note = context['object'].note
context["aliases"] = AliasTable( context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
note.alias_set.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all()) .filter_queryset(self.request.user, Alias, "view")).all())
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias( context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
note=context["object"].note, note=context["object"].note,
name="", name="",
@ -458,8 +450,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
note = context['object'].note note = context['object'].note
context["aliases"] = AliasTable(note.alias_set.filter( context["aliases"] = AliasTable(note.alias_set.filter(PermissionBackend
PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all()) .filter_queryset(self.request.user, Alias, "view")).all())
context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias( context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
note=context["object"].note, note=context["object"].note,
name="", name="",
@ -678,13 +670,11 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"): if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
if not last_name: if not last_name:
form.add_error('last_name', _("This field is required.")) form.add_error('last_name', _("This field is required."))
error = True
if not first_name: if not first_name:
form.add_error('first_name', _("This field is required.")) form.add_error('first_name', _("This field is required."))
error = True
if not bank and credit_type.special_type == "Chèque": if not bank and credit_type.special_type == "Chèque":
form.add_error('bank', _("This field is required.")) form.add_error('bank', _("This field is required."))
error = True return self.form_invalid(form)
return not error return not error

View File

@ -799,12 +799,12 @@
"member", "member",
"membership" "membership"
], ],
"query": "{}", "query": "{\"club\": [\"club\"]}",
"type": "change", "type": "change",
"mask": 3, "mask": 3,
"field": "roles", "field": "roles",
"permanent": false, "permanent": false,
"description": "Modifier les rôles d'une adhésion" "description": "Modifier les rôles d'un adhérent d'un club"
} }
}, },
{ {
@ -2081,7 +2081,7 @@
], ],
"query": "{}", "query": "{}",
"type": "change", "type": "change",
"mask": 2, "mask": 1,
"field": "invalidity_reason", "field": "invalidity_reason",
"permanent": false, "permanent": false,
"description": "Modifier la raison d'invalidité d'une transaction" "description": "Modifier la raison d'invalidité d'une transaction"
@ -2807,70 +2807,6 @@
"description": "Voir ses propres alias, pour toujours" "description": "Voir ses propres alias, pour toujours"
} }
}, },
{
"model": "permission.permission",
"pk": 180,
"fields": {
"model": [
"auth",
"user"
],
"query": "{\"profile__registration_valid\": false}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir n'importe quel utilisateur non encore inscrit"
}
},
{
"model": "permission.permission",
"pk": 181,
"fields": {
"model": [
"member",
"profile"
],
"query": "{\"registration_valid\": false}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir n'importe quel profil non encore inscrit"
}
},
{
"model": "permission.permission",
"pk": 182,
"fields": {
"model": [
"auth",
"user"
],
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
}
},
{
"model": "permission.permission",
"pk": 183,
"fields": {
"model": [
"note",
"note"
],
"query": "{}",
"type": "change",
"mask": 1,
"field": "display_image",
"permanent": false,
"description": "Changer l'image de n'importe quelle note"
}
},
{ {
"model": "permission.role", "model": "permission.role",
"pk": 1, "pk": 1,
@ -3003,14 +2939,14 @@
62, 62,
127, 127,
133, 133,
135,
136, 136,
141, 141,
142, 142,
150, 150,
166, 166,
167, 167,
168, 168
182
] ]
} }
}, },
@ -3086,8 +3022,7 @@
175, 175,
176, 176,
177, 177,
178, 178
183
] ]
} }
}, },
@ -3270,12 +3205,7 @@
175, 175,
176, 176,
177, 177,
178, 178
179,
180,
181,
182,
183
] ]
} }
}, },
@ -3309,12 +3239,7 @@
170, 170,
171, 171,
176, 176,
177, 177
178,
179,
180,
181,
182
] ]
} }
}, },
@ -3477,6 +3402,7 @@
135, 135,
136, 136,
137, 137,
138,
139, 139,
140, 140,
143, 143,
@ -3489,41 +3415,6 @@
] ]
} }
}, },
{
"model": "permission.role",
"pk": 20,
"fields": {
"for_club": 2,
"name": "PC Kfet",
"permissions": [
6,
22,
24,
25,
26,
27,
30,
49,
50,
55,
56,
57,
58,
137,
143,
147,
150,
166,
167,
168,
176,
177,
180,
181,
182
]
}
},
{ {
"model": "wei.weirole", "model": "wei.weirole",
"pk": 12, "pk": 12,

View File

@ -45,7 +45,6 @@ class InstancedPermission:
with transaction.atomic(): with transaction.atomic():
sid = transaction.savepoint() sid = transaction.savepoint()
for o in self.model.model_class().objects.filter(pk=0).all(): for o in self.model.model_class().objects.filter(pk=0).all():
o._no_signal = True
o._force_delete = True o._force_delete = True
Model.delete(o) Model.delete(o)
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins. # An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
@ -63,6 +62,10 @@ class InstancedPermission:
obj._no_signal = True obj._no_signal = True
Model.save(obj, force_insert=True) Model.save(obj, force_insert=True)
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists() ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
# Delete testing object
obj._no_signal = True
obj._force_delete = True
Model.delete(obj)
transaction.savepoint_rollback(sid) transaction.savepoint_rollback(sid)
return ret return ret

View File

@ -51,10 +51,8 @@ class ProtectQuerysetMixin:
# No worry if the user change the hidden fields: a 403 error will be performed if the user tries to make # No worry if the user change the hidden fields: a 403 error will be performed if the user tries to make
# a custom request. # a custom request.
# We could also delete the field, but some views might be affected. # We could also delete the field, but some views might be affected.
meta = form.instance._meta
for key in form.base_fields: for key in form.base_fields:
if not PermissionBackend.check_perm(self.request.user, if not PermissionBackend.check_perm(self.request.user, "wei.change_weiregistration_" + key, self.object):
f"{meta.app_label}.change_{meta.model_name}_" + key, self.object):
form.fields[key].widget = HiddenInput() form.fields[key].widget = HiddenInput()
return form return form

View File

@ -4,8 +4,6 @@
import django_tables2 as tables import django_tables2 as tables
from django.contrib.auth.models import User from django.contrib.auth.models import User
from treasury.models import SogeCredit
class FutureUserTable(tables.Table): class FutureUserTable(tables.Table):
""" """
@ -23,7 +21,6 @@ class FutureUserTable(tables.Table):
fields = ('last_name', 'first_name', 'username', 'email', ) fields = ('last_name', 'first_name', 'username', 'email', )
model = User model = User
row_attrs = { row_attrs = {
'class': lambda record: 'table-row' 'class': 'table-row',
+ (' bg-warning' if SogeCredit.objects.filter(user=record).exists() else ''),
'data-href': lambda record: record.pk 'data-href': lambda record: record.pk
} }

View File

@ -235,7 +235,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
fee += 8000 fee += 8000
ctx["total_fee"] = "{:.02f}".format(fee / 100, ) ctx["total_fee"] = "{:.02f}".format(fee / 100, )
ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() ctx["declare_soge_account"] = True
return ctx return ctx

View File

@ -28,8 +28,6 @@ class TreasuryConfig(AppConfig):
source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy=None, specialtransactionproxy=None,
): ):
proxy = SpecialTransactionProxy(transaction=transaction, remittance=None) SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None)
proxy._force_save = True
proxy.save()
post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy) post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy)

View File

@ -10,8 +10,9 @@ def save_special_transaction(instance, created, **kwargs):
""" """
if not hasattr(instance, "_no_signal"): if not hasattr(instance, "_no_signal"):
if created and RemittanceType.objects.filter( if instance.is_credit():
note=instance.source if instance.is_credit() else instance.destination).exists(): if created and RemittanceType.objects.filter(note=instance.source).exists():
proxy = SpecialTransactionProxy(transaction=instance, remittance=None) SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()
proxy._force_save = True else:
proxy.save() if created and RemittanceType.objects.filter(note=instance.destination).exists():
SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save()

View File

@ -147,4 +147,4 @@ class SogeCreditTable(tables.Table):
class Meta: class Meta:
model = SogeCredit model = SogeCredit
fields = ('user', 'user__last_name', 'user__first_name', 'amount', 'valid', ) fields = ('user', 'amount', 'valid', )

View File

@ -11,14 +11,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
<dt class="col-xl-6 text-right">{% trans 'last name'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'user'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user.last_name }}</dd> <dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user }}</a></dd>
<dt class="col-xl-6 text-right">{% trans 'first name'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user.first_name }}</dd>
<dt class="col-xl-6 text-right">{% trans 'username'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url 'member:user_detail' pk=object.user.pk %}">{{ object.user.username }}</a></dd>
{% if "note.view_note_balance"|has_perm:object.user.note %} {% if "note.view_note_balance"|has_perm:object.user.note %}
<dt class="col-xl-6 text-right">{% trans 'balance'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'balance'|capfirst %}</dt>

View File

@ -2,12 +2,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.contrib.auth import login
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
from django.contrib.sessions.backends.db import SessionStore
from threading import local from threading import local
from django.contrib.sessions.backends.db import SessionStore
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session') SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip') IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
@ -78,41 +78,6 @@ class SessionMiddleware(object):
return response 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): class TurbolinksMiddleware(object):
""" """
Send the `Turbolinks-Location` header in response to a visit that was redirected, Send the `Turbolinks-Location` header in response to a visit that was redirected,

View File

@ -49,6 +49,9 @@ try:
except ImportError: except ImportError:
pass pass
if "logs" in INSTALLED_APPS:
MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
if DEBUG: if DEBUG:
PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor'] PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor']
if "debug_toolbar" in INSTALLED_APPS: if "debug_toolbar" in INSTALLED_APPS:

View File

@ -79,8 +79,6 @@ MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware',
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware', 'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
'note_kfet.middlewares.SessionMiddleware',
'note_kfet.middlewares.LoginByIPMiddleware',
'note_kfet.middlewares.TurbolinksMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware',
] ]

View File

@ -7,7 +7,7 @@ django-extensions~=2.1.4
django-filter~=2.1.0 django-filter~=2.1.0
django-htcpcp-tea~=0.3.1 django-htcpcp-tea~=0.3.1
django-mailer~=2.0.1 django-mailer~=2.0.1
django-oauth-toolkit~=1.3.3 django-oauth-toolkit~=1.1.2
django-phonenumber-field~=5.0.0 django-phonenumber-field~=5.0.0
django-polymorphic~=2.0.3 django-polymorphic~=2.0.3
djangorestframework~=3.9.0 djangorestframework~=3.9.0