1
0
mirror of https://gitlab.crans.org/mediatek/med.git synced 2025-06-21 01:18:21 +02:00

Initial commit, projet med

This commit is contained in:
Med
2017-06-30 03:25:07 +02:00
parent 287d60db54
commit 9501a10eac
71 changed files with 1795 additions and 591 deletions

View File

@ -25,7 +25,7 @@ from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from reversion.admin import VersionAdmin
from .models import User, Machine, Request
from .models import User, Right, ListRight, Request
from .forms import UserChangeForm, UserCreationForm
@ -42,8 +42,11 @@ class UserAdmin(admin.ModelAdmin):
class RequestAdmin(admin.ModelAdmin):
list_display = ('user', 'type', 'created_at', 'expires_at')
class MachineAdmin(VersionAdmin):
list_display = ('mac_address','proprio')
class RightAdmin(VersionAdmin):
list_display = ('user', 'right')
class ListRightAdmin(VersionAdmin):
list_display = ('listright',)
class UserAdmin(VersionAdmin, BaseUserAdmin):
# The forms to add and change user instances
@ -72,9 +75,10 @@ class UserAdmin(VersionAdmin, BaseUserAdmin):
ordering = ('pseudo',)
filter_horizontal = ()
admin.site.register(Machine, MachineAdmin)
admin.site.register(User, UserAdmin)
admin.site.register(Request, RequestAdmin)
admin.site.register(ListRight, ListRightAdmin)
admin.site.register(Right, RightAdmin)
# Now register the new UserAdmin...
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

View File

@ -1,44 +0,0 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Ce script est appellé avant le démarage du portail, il insère les bonnes règles
# dans l'iptables et active le routage
from django.core.management.base import BaseCommand, CommandError
from users.models import restore_iptables, create_ip_set, fill_ipset, apply
from portail_captif.settings import AUTORIZED_INTERFACES
class Command(BaseCommand):
help = 'Mets en place iptables et le set ip au démarage'
def handle(self, *args, **options):
# Creation de l'ipset
create_ip_set()
# Remplissage avec les macs autorisées
fill_ipset()
# Restauration de l'iptables
restore_iptables()
# Activation du routage sur les bonnes if
for interface in AUTORIZED_INTERFACES:
apply(["sudo", "-n", "sysctl", "net.ipv6.conf.%s.forwarding=1" % interface])
apply(["sudo", "-n", "sysctl", "net.ipv4.conf.%s.forwarding=1" % interface])

View File

@ -1,41 +0,0 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Ce script est appellé avant le démarage du portail, il insère les bonnes règles
# dans l'iptables et active le routage
from django.core.management.base import BaseCommand, CommandError
from users.models import restore_iptables, create_ip_set, fill_ipset, disable_iptables, apply
from portail_captif.settings import AUTORIZED_INTERFACES
class Command(BaseCommand):
help = 'Mets en place iptables et le set ip au démarage'
def handle(self, *args, **options):
# Destruction de l'iptables
disable_iptables()
# Desactivation du routage sur les bonnes if
for interface in AUTORIZED_INTERFACES:
apply(["sudo", "-n", "sysctl", "net.ipv6.conf.%s.forwarding=0" % interface])
apply(["sudo", "-n", "sysctl", "net.ipv4.conf.%s.forwarding=0" % interface])

View File

@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-28 10:30
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
initial = True
dependencies = [
]
@ -15,16 +18,20 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, verbose_name='last login', null=True)),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('name', models.CharField(max_length=255)),
('surname', models.CharField(max_length=255)),
('email', models.EmailField(max_length=254)),
('state', models.IntegerField(default=0, choices=[(0, 'STATE_ACTIVE'), (1, 'STATE_DISABLED'), (2, 'STATE_ARCHIVE')])),
('pseudo', models.CharField(max_length=32, unique=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('telephone', models.CharField(blank=True, max_length=15, null=True)),
('adresse', models.CharField(blank=True, max_length=255, null=True)),
('maxemprunt', models.IntegerField(default=5)),
('state', models.IntegerField(choices=[(0, 'STATE_ACTIVE'), (1, 'STATE_DISABLED'), (2, 'STATE_ARCHIVE')], default=0)),
('pseudo', models.CharField(help_text='Doit contenir uniquement des lettres, chiffres, ou tirets. ', max_length=32, unique=True)),
('comment', models.CharField(blank=True, help_text='Commentaire, promo', max_length=255)),
('registered', models.DateTimeField(auto_now_add=True)),
('admin', models.BooleanField(default=False)),
('right', models.IntegerField(choices=[(0, 'BASIC'), (1, 'PERM'), (2, 'BUREAU')], default=0)),
],
options={
'abstract': False,
@ -33,8 +40,8 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Request',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('type', models.CharField(max_length=2, choices=[('PW', 'Mot de passe'), ('EM', 'Email')])),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(choices=[('PW', 'Mot de passe'), ('EM', 'Email')], max_length=2)),
('token', models.CharField(max_length=32)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField()),

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='comment',
field=models.CharField(blank=True, help_text='Commentaire, promo', max_length=255),
),
migrations.AlterField(
model_name='user',
name='pseudo',
field=models.CharField(unique=True, help_text='Doit contenir uniquement des lettres, chiffres, ou tirets. ', max_length=32),
),
]

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-29 12:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='maxemprunt',
field=models.IntegerField(default=5, help_text="Maximum d'emprunts autorisés"),
),
migrations.AlterField(
model_name='user',
name='right',
field=models.IntegerField(choices=[(0, 'BASIC'), (1, 'PERM'), (2, 'BUREAU')], default=0, help_text='Droits accordés'),
),
]

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-29 19:56
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20170629_1438'),
]
operations = [
migrations.CreateModel(
name='ListRight',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('listright', models.CharField(max_length=255, unique=True)),
('details', models.CharField(blank=True, help_text='Description', max_length=255)),
],
),
migrations.CreateModel(
name='Right',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('right', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='users.ListRight')),
],
),
migrations.RemoveField(
model_name='user',
name='right',
),
migrations.AddField(
model_name='right',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.AlterUniqueTogether(
name='right',
unique_together=set([('user', 'right')]),
),
]

View File

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import macaddress.fields
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20170610_1550'),
]
operations = [
migrations.CreateModel(
name='Machines',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('mac_address', macaddress.fields.MACAddressField(unique=True, max_length=17, integer=False)),
('proprio', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=django.db.models.deletion.PROTECT)),
],
),
]

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import macaddress.fields
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('users', '0003_machines'),
]
operations = [
migrations.CreateModel(
name='Machine',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('mac_address', macaddress.fields.MACAddressField(max_length=17, unique=True, integer=False)),
('proprio', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=django.db.models.deletion.PROTECT)),
],
),
migrations.RemoveField(
model_name='machines',
name='proprio',
),
migrations.DeleteModel(
name='Machines',
),
]

View File

@ -28,9 +28,8 @@ from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.utils.functional import cached_property
from macaddress.fields import MACAddressField
from portail_captif.settings import GENERIC_IPSET_COMMAND, IPSET_NAME, REQ_EXPIRE_HRS,FORBIDEN_INTERFACES, SERVER_SELF_IP, AUTORIZED_INTERFACES, PORTAIL_ACTIVE
from med.settings import MAX_EMPRUNT, REQ_EXPIRE_HRS
import re, uuid
import datetime
@ -38,116 +37,6 @@ from django.utils import timezone
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
import subprocess
def apply(cmd):
return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def mac_from_ip(ip):
cmd = ['/usr/sbin/arp','-na',ip]
p = apply(cmd)
output, errors = p.communicate()
if output is not None :
mac_addr = output.decode().split()[3]
return str(mac_addr)
else:
return None
def create_ip_set():
command_to_execute = ["sudo", "-n"] + GENERIC_IPSET_COMMAND.split() + ["create",IPSET_NAME,"hash:mac","hashsize","1024","maxelem","65536"]
apply(command_to_execute)
command_to_execute = ["sudo", "-n"] + GENERIC_IPSET_COMMAND.split() + ["flush",IPSET_NAME]
apply(command_to_execute)
return
def fill_ipset():
all_machines = Machine.objects.filter(proprio__in=User.objects.filter(state=User.STATE_ACTIVE))
ipset = "%s\nCOMMIT\n" % '\n'.join(["add %s %s" % (IPSET_NAME, str(machine.mac_address)) for machine in all_machines])
command_to_execute = ["sudo", "-n"] + GENERIC_IPSET_COMMAND.split() + ["restore"]
process = apply(command_to_execute)
process.communicate(input=ipset.encode('utf-8'))
return
class iptables:
def __init__(self):
self.nat = "\n*nat"
self.mangle = "\n*mangle"
self.filter = "\n*filter"
def commit(self, chain):
self.add(chain, "COMMIT\n")
def add(self, chain, value):
setattr(self, chain, getattr(self, chain) + "\n" + value)
def init_filter(self, subchain, decision="ACCEPT"):
self.add("filter", ":" + subchain + " " + decision)
def init_nat(self, subchain, decision="ACCEPT"):
self.add("nat", ":" + subchain + " " + decision)
def init_mangle(self, subchain, decision="ACCEPT"):
self.add("mangle", ":" + subchain + " " + decision)
def jump(self, chain, subchainA, subchainB):
self.add(chain, "-A " + subchainA + " -j " + subchainB)
def gen_filter(ipt):
ipt.init_filter("INPUT")
ipt.init_filter("FORWARD")
ipt.init_filter("OUTPUT")
for interface in FORBIDEN_INTERFACES:
ipt.add("filter", "-A FORWARD -o %s -j REJECT --reject-with icmp-port-unreachable" % interface)
ipt.commit("filter")
return ipt
def gen_nat(ipt, nat_active=True):
ipt.init_nat("PREROUTING")
ipt.init_nat("INPUT")
ipt.init_nat("OUTPUT")
ipt.init_nat("POSTROUTING")
if nat_active:
ipt.init_nat("CAPTIF", decision="-")
ipt.jump("nat", "PREROUTING", "CAPTIF")
ipt.jump("nat", "POSTROUTING", "MASQUERADE")
if PORTAIL_ACTIVE:
ipt.add("nat", "-A CAPTIF -m set ! --match-set %s src -j DNAT --to-destination %s" % (IPSET_NAME, SERVER_SELF_IP))
ipt.jump("nat", "CAPTIF", "RETURN")
ipt.commit("nat")
return ipt
def gen_mangle(ipt):
ipt.init_mangle("PREROUTING")
ipt.init_mangle("INPUT")
ipt.init_mangle("FORWARD")
ipt.init_mangle("OUTPUT")
ipt.init_mangle("POSTROUTING")
for interface in AUTORIZED_INTERFACES:
ipt.add("mangle", """-A PREROUTING -i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL " """ % interface)
ipt.commit("mangle")
return ipt
def restore_iptables():
""" Restrore l'iptables pour la création du portail. Est appellé par root au démarage du serveur"""
ipt = iptables()
gen_mangle(ipt)
gen_nat(ipt)
gen_filter(ipt)
global_chain = ipt.nat + ipt.filter + ipt.mangle
command_to_execute = ["sudo","-n","/sbin/iptables-restore"]
process = apply(command_to_execute)
process.communicate(input=global_chain.encode('utf-8'))
return
def disable_iptables():
""" Insère une iptables minimaliste sans nat"""
ipt = iptables()
gen_mangle(ipt)
gen_filter(ipt)
gen_nat(ipt, nat_active=False)
global_chain = ipt.nat + ipt.filter + ipt.mangle
command_to_execute = ["sudo","-n","/sbin/iptables-restore"]
process = apply(command_to_execute)
process.communicate(input=global_chain.encode('utf-8'))
return
class UserManager(BaseUserManager):
def _create_user(self, pseudo, name, surname, email, password=None, su=False):
@ -193,15 +82,17 @@ class User(AbstractBaseUser):
(2, 'STATE_ARCHIVE'),
)
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
email = models.EmailField()
email = models.EmailField(null=True, blank=True)
telephone = models.CharField(max_length=15, null=True, blank=True)
adresse = models.CharField(max_length=255, null=True, blank=True)
maxemprunt = models.IntegerField(default=MAX_EMPRUNT, help_text="Maximum d'emprunts autorisés")
state = models.IntegerField(choices=STATES, default=STATE_ACTIVE)
pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets. ")
comment = models.CharField(help_text="Commentaire, promo", max_length=255, blank=True)
registered = models.DateTimeField(auto_now_add=True)
admin = models.BooleanField(default=False)
USERNAME_FIELD = 'pseudo'
REQUIRED_FIELDS = ['name', 'surname', 'email']
@ -218,7 +109,11 @@ class User(AbstractBaseUser):
@property
def is_admin(self):
return self.admin
try:
Right.objects.get(user=self, right__listright='admin')
except Right.DoesNotExist:
return False
return True
@is_admin.setter
def is_admin(self, value):
@ -229,9 +124,11 @@ class User(AbstractBaseUser):
def has_perms(self, perms, obj=None):
for perm in perms:
if perm=="admin":
return self.is_admin
return False
try:
Right.objects.get(user=self, right__listright=perm)
return True
except Right.DoesNotExist:
return False
def get_full_name(self):
return '%s %s' % (self.name, self.surname)
@ -242,48 +139,33 @@ class User(AbstractBaseUser):
def has_perm(self, perm, obj=None):
return True
def has_right(self, right):
return Right.objects.filter(user=self).filter(right=ListRight.objects.get(listright=right)).exists()
def has_module_perms(self, app_label):
# Simplest version again
return True
def get_admin_right(self):
admin, created = ListRight.objects.get_or_create(listright="admin")
return admin
def make_admin(self):
""" Make User admin """
self.admin = True
self.save()
user_admin_right = Right(user=self, right=self.get_admin_right())
user_admin_right.save()
def un_admin(self):
self.admin = False
self.save()
def machines(self):
return Machine.objects.filter(proprio=self)
try:
user_right = Right.objects.get(user=self,right=self.get_admin_right())
except Right.DoesNotExist:
return
user_right.delete()
def __str__(self):
return self.name + " " + self.surname
return self.pseudo
class Machine(models.Model):
proprio = models.ForeignKey('User', on_delete=models.PROTECT)
mac_address = MACAddressField(integer=False, unique=True)
def add_to_set(self):
command_to_execute = ["sudo", "-n"] + GENERIC_IPSET_COMMAND.split() + ["add",IPSET_NAME,str(self.mac_address)]
apply(command_to_execute)
def del_to_set(self):
command_to_execute = ["sudo", "-n"] + GENERIC_IPSET_COMMAND.split() + ["del",IPSET_NAME,str(self.mac_address)]
apply(command_to_execute)
@receiver(post_save, sender=Machine)
def machine_post_save(sender, **kwargs):
machine = kwargs['instance']
machine.add_to_set()
@receiver(post_delete, sender=Machine)
def machine_post_delete(sender, **kwargs):
machine = kwargs['instance']
machine.del_to_set()
class Request(models.Model):
PASSWD = 'PW'
EMAIL = 'EM'
@ -305,6 +187,27 @@ class Request(models.Model):
self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens
super(Request, self).save()
class Right(models.Model):
PRETTY_NAME = "Droits affectés à des users"
user = models.ForeignKey('User', on_delete=models.PROTECT)
right = models.ForeignKey('ListRight', on_delete=models.PROTECT)
class Meta:
unique_together = ("user", "right")
def __str__(self):
return str(self.user) + " - " + str(self.right)
class ListRight(models.Model):
PRETTY_NAME = "Liste des droits existants"
listright = models.CharField(max_length=255, unique=True)
details = models.CharField(help_text="Description", max_length=255, blank=True)
def __str__(self):
return self.listright
class BaseInfoForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BaseInfoForm, self).__init__(*args, **kwargs)
@ -319,33 +222,22 @@ class BaseInfoForm(ModelForm):
'pseudo',
'surname',
'email',
]
class EditInfoForm(BaseInfoForm):
class Meta(BaseInfoForm.Meta):
fields = [
'name',
'pseudo',
'surname',
'comment',
'email',
'admin',
'telephone',
'adresse',
]
class InfoForm(BaseInfoForm):
class Meta(BaseInfoForm.Meta):
fields = [
fields = [
'name',
'pseudo',
'comment',
'surname',
'email',
'admin',
'telephone',
'adresse',
'maxemprunt',
]
class UserForm(EditInfoForm):
class Meta(EditInfoForm.Meta):
fields = '__all__'
class PasswordForm(ModelForm):
class Meta:
@ -357,7 +249,35 @@ class StateForm(ModelForm):
model = User
fields = ['state']
class MachineForm(ModelForm):
class RightForm(ModelForm):
def __init__(self, *args, **kwargs):
super(RightForm, self).__init__(*args, **kwargs)
self.fields['right'].label = 'Droit'
self.fields['right'].empty_label = "Choisir un nouveau droit"
class Meta:
model = Machine
exclude = '__all__'
model = Right
fields = ['right']
class DelRightForm(Form):
rights = forms.ModelMultipleChoiceField(queryset=Right.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple)
class ListRightForm(ModelForm):
class Meta:
model = ListRight
fields = ['listright', 'details']
def __init__(self, *args, **kwargs):
super(ListRightForm, self).__init__(*args, **kwargs)
self.fields['listright'].label = 'Nom du droit/groupe'
class NewListRightForm(ListRightForm):
class Meta(ListRightForm.Meta):
fields = '__all__'
class DelListRightForm(Form):
listrights = forms.ModelMultipleChoiceField(queryset=ListRight.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple)

View File

@ -0,0 +1,44 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
<table class="table table-striped">
<thead>
<tr>
<th>Droit</th>
<th>Details</th>
<th></th>
<th></th>
</tr>
</thead>
{% for listright in listright_list %}
<tr>
<td>{{ listright.listright }}</td>
<td>{{ listright.details }}</td>
<td class="text-right">
{% include 'buttons/edit.html' with href='users:edit-listright' id=listright.id %}
{% include 'buttons/history.html' with href='users:history' name='listright' id=listright.id %}
</td>
</tr>
{% endfor %}
</table>

View File

@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>Nom</th>
<th>Pseudo</th>
<th>Mail</th>
<th>Machines</th>
<th>Max emprunts</th>
<th>Profil</th>
</tr>
</thead>
@ -43,13 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ user.surname }}</td>
<td>{{ user.pseudo }}</td>
<td>{{ user.email }}</td>
<td><table class="table table-striped">
{% for machine in user.machines %}
<tr>
<td>{{ machine.mac_address }}</td>
</tr>
{% endfor %}
</table>
<td>{{ user.maxemprunt }}</td>
</td>
<td><a href="{% url "users:profil" user.id%}" class="btn btn-primary btn-sm" role="button"><i class="glyphicon glyphicon-user"></i></a>
</td>

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "users/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en

View File

@ -0,0 +1,38 @@
{% extends "users/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% block title %}Utilisateurs{% endblock %}
{% block content %}
<h2>Liste des droits</h2>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:add-listright' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un droit ou groupe</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'users:del-listright' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs droits/groupes</a>
{% include "users/aff_listright.html" with listright_list=listright_list %}
<br />
<br />
<br />
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "users/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
@ -38,11 +38,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="glyphicon glyphicon-lock"></i>
Changer le mot de passe
</a>
{% if is_admin %}
{% if is_bureau %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:state' user.id %}">
<i class="glyphicon glyphicon-flash"></i>
Changer le statut
</a>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:add-right' user.id %}">
<i class="glyphicon glyphicon-ok"></i>
Ajouter un droit
</a>
{% endif %}
<a class="btn btn-info btn-sm" role="button" href="{% url 'users:history' 'user' user.id %}">
<i class="glyphicon glyphicon-time"></i>
@ -71,7 +75,24 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>Date d'inscription</th>
<td>{{ user.registered }}</td>
</tr>
<th>Statut</th>
<tr>
<th>Adresse</th>
<td>{{ user.adresse }}</td>
<th>Telephone</th>
<td>{{ user.telephone }}</td>
</tr>
<tr>
<th>Emprunts maximums autorisés</th>
<td>{{ user.maxemprunt }}</td>
<th>Droits</th>
{% if list_droits %}
<td>{% for droit in list_droits %}{{ droit.right }}{% if list_droits|length != forloop.counter %} - {% endif %} {% endfor %}</td>
{% else %}
<td>Aucun</td>
{% endif %}
</tr>
<tr>
<th>Statut</th>
{% if user.state == 0 %}
<td><font color="green">Actif</font></td>
{% elif user.state == 1 %}
@ -83,28 +104,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ user.last_login }}</td>
</tr>
</table>
<h2>Machines enregistrées</h2>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'users:capture' %}">
<i class="glyphicon glyphicon-flash"></i>
Enregistrer la machine utilisée actuellement
</a>
<table class="table table-striped">
<thead>
<tr>
<th>Adresse mac</th>
<th>Historique</th>
</tr>
</thead>
{% for machine in machines_list %}
<tr>
<td>{{ machine.mac_address }}</td>
<td><a class="btn btn-info btn-sm" role="button" href="{% url 'users:history' 'machines' machine.id %}">
<i class="glyphicon glyphicon-time"></i>
</a>
</td>
</tr>
{% endfor %}
</table>
<h2>Emprunts</h2>
<h4><a class="btn btn-primary btn-sm" role="button" href="{% url 'media:add-emprunt' user.id %}"><i class="glyphicon glyphicon-flag"></i> Ajouter</a></h4>
{% if emprunts_list %}
{% include "media/aff_emprunts.html" with emprunts_list=emprunts_list %}
{% else %}
<p>Aucun emprunt</p>
{% endif %}
<br />
<br />
<br />

View File

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% block sidebar %}
{% if is_perm %}
<a class="list-group-item list-group-item-success" href="{% url "users:new-user" %}">
<i class="glyphicon glyphicon-plus"></i>
Créer un adhérent
</a>
<a class="list-group-item list-group-item-info" href="{% url "users:index" %}">
<i class="glyphicon glyphicon-list"></i>
Adhérents
</a>
<a class="list-group-item list-group-item-info" href="{% url "users:index-listright" %}">
<i class="glyphicon glyphicon-list"></i>
Droits
</a>
{% endif %}
{% if is_bureau %}
<a class="list-group-item list-group-item-danger" href="{% url "users:del-right" %}">
<i class="glyphicon glyphicon-trash"></i>
Retirer un droit
</a>
{% endif %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "users/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en

View File

@ -27,16 +27,21 @@ from . import views
app_name = 'users'
urlpatterns = [
url(r'^new_user/$', views.new_user, name='new-user'),
url(r'^capture/$', views.capture, name='capture'),
url(r'^edit_info/(?P<userid>[0-9]+)$', views.edit_info, name='edit-info'),
url(r'^state/(?P<userid>[0-9]+)$', views.state, name='state'),
url(r'^password/(?P<userid>[0-9]+)$', views.password, name='password'),
url(r'^profil/(?P<userid>[0-9]+)$', views.profil, name='profil'),
url(r'^mon_profil/$', views.mon_profil, name='mon-profil'),
url(r'^add_listright/$', views.add_listright, name='add-listright'),
url(r'^edit_listright/(?P<listrightid>[0-9]+)$', views.edit_listright, name='edit-listright'),
url(r'^del_listright/$', views.del_listright, name='del-listright'),
url(r'^index_listright/$', views.index_listright, name='index-listright'),
url(r'^add_right/(?P<userid>[0-9]+)$', views.add_right, name='add-right'),
url(r'^del_right/$', views.del_right, name='del-right'),
url(r'^process/(?P<token>[a-z0-9]{32})/$', views.process, name='process'),
url(r'^reset_password/$', views.reset_password, name='reset-password'),
url(r'^history/(?P<object>user)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>machines)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>listright)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^$', views.index, name='index'),
]

View File

@ -20,7 +20,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# App de gestion des users pour portail_captif
# App de gestion des users pour med
# Goulven Kermarec, Gabriel Détraz, Lemesle Augustin
# Gplv2
from django.shortcuts import get_object_or_404, render, redirect
@ -39,13 +39,12 @@ from django.db import transaction
from reversion.models import Version
from reversion import revisions as reversion
from users.models import User, MachineForm, Request
from users.models import EditInfoForm, InfoForm, BaseInfoForm, Machine, StateForm, mac_from_ip
from users.models import User, Request, ListRight, Right, DelListRightForm, NewListRightForm, ListRightForm, RightForm, DelRightForm
from users.models import InfoForm, BaseInfoForm, StateForm
from users.forms import PassForm, ResetPasswordForm
import ipaddress
import subprocess
from media.models import Emprunt
from portail_captif.settings import REQ_EXPIRE_STR, EMAIL_FROM, ASSO_NAME, ASSO_EMAIL, SITE_NAME, CAPTIVE_IP_RANGE, CAPTIVE_WIFI, PAGINATION_NUMBER
from med.settings import REQ_EXPIRE_STR, EMAIL_FROM, ASSO_NAME, ASSO_EMAIL, SITE_NAME, PAGINATION_NUMBER
def form(ctx, template, request):
@ -85,6 +84,8 @@ def reset_passwd_mail(req, request):
return
@login_required
@permission_required('perm')
def new_user(request):
""" Vue de création d'un nouvel utilisateur, envoie un mail pour le mot de passe"""
user = BaseInfoForm(request.POST or None)
@ -111,10 +112,10 @@ def edit_info(request, userid):
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
if not request.user.is_admin and user != request.user:
if not request.user.has_perms(('perm',)) and user != request.user:
messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit admin")
return redirect("/users/profil/" + str(request.user.id))
if not request.user.is_admin:
if not request.user.has_perms(('perm',)):
user = BaseInfoForm(request.POST or None, instance=user)
else:
user = InfoForm(request.POST or None, instance=user)
@ -128,7 +129,7 @@ def edit_info(request, userid):
return form({'userform': user}, 'users/user.html', request)
@login_required
@permission_required('admin')
@permission_required('bureau')
def state(request, userid):
""" Changer l'etat actif/desactivé/archivé d'un user, need droit bureau """
try:
@ -156,7 +157,7 @@ def password(request, userid):
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
if not request.user.is_admin and user != request.user:
if not request.user.has_perms(('perm',)) and user != request.user:
messages.error(request, "Vous ne pouvez pas modifier un autre user que vous sans droit admin")
return redirect("/users/profil/" + str(request.user.id))
u_form = PassForm(request.POST or None)
@ -165,7 +166,108 @@ def password(request, userid):
return form({'userform': u_form}, 'users/user.html', request)
@login_required
@permission_required('admin')
@permission_required('bureau')
def add_listright(request):
""" Ajouter un droit/groupe, nécessite droit bureau.
Obligation de fournir un gid pour la synchro ldap, unique """
listright = NewListRightForm(request.POST or None)
if listright.is_valid():
with transaction.atomic(), reversion.create_revision():
listright.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Le droit/groupe a été ajouté")
return redirect("/users/index_listright/")
return form({'userform': listright}, 'users/user.html', request)
@login_required
@permission_required('bureau')
def edit_listright(request, listrightid):
""" Editer un groupe/droit, necessite droit bureau, à partir du listright id """
try:
listright_instance = ListRight.objects.get(pk=listrightid)
except ListRight.DoesNotExist:
messages.error(request, u"Entrée inexistante" )
return redirect("/users/")
listright = ListRightForm(request.POST or None, instance=listright_instance)
if listright.is_valid():
with transaction.atomic(), reversion.create_revision():
listright.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in listright.changed_data))
messages.success(request, "Droit modifié")
return redirect("/users/index_listright/")
return form({'userform': listright}, 'users/user.html', request)
@login_required
@permission_required('bureau')
def del_listright(request):
""" Supprimer un ou plusieurs groupe, possible si il est vide, need droit bureau """
listright = DelListRightForm(request.POST or None)
if listright.is_valid():
listright_dels = listright.cleaned_data['listrights']
for listright_del in listright_dels:
try:
with transaction.atomic(), reversion.create_revision():
listright_del.delete()
reversion.set_comment("Destruction")
messages.success(request, "Le droit/groupe a été supprimé")
except ProtectedError:
messages.error(
request,
"L'établissement %s est affecté à au moins un user, \
vous ne pouvez pas le supprimer" % listright_del)
return redirect("/users/index_listright/")
return form({'userform': listright}, 'users/user.html', request)
@login_required
@permission_required('bureau')
def add_right(request, userid):
""" Ajout d'un droit à un user, need droit bureau """
try:
user = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
right = RightForm(request.POST or None)
if right.is_valid():
right = right.save(commit=False)
right.user = user
try:
with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user)
reversion.set_comment("Ajout du droit %s" % right.right)
right.save()
messages.success(request, "Droit ajouté")
except IntegrityError:
pass
return redirect("/users/profil/" + userid)
return form({'userform': right}, 'users/user.html', request)
@login_required
@permission_required('bureau')
def del_right(request):
""" Supprimer un droit à un user, need droit bureau """
user_right_list = DelRightForm(request.POST or None)
if user_right_list.is_valid():
right_del = user_right_list.cleaned_data['rights']
with transaction.atomic(), reversion.create_revision():
reversion.set_user(request.user)
reversion.set_comment("Retrait des droit %s" % ','.join(str(deleted_right) for deleted_right in right_del))
right_del.delete()
messages.success(request, "Droit retiré avec succès")
return redirect("/users/")
return form({'userform': user_right_list}, 'users/user.html', request)
@login_required
@permission_required('perm')
def index_listright(request):
""" Affiche l'ensemble des droits , need droit perm """
listright_list = ListRight.objects.order_by('listright')
return render(request, 'users/index_listright.html', {'listright_list':listright_list})
@login_required
@permission_required('perm')
def index(request):
""" Affiche l'ensemble des users, need droit admin """
users_list = User.objects.order_by('state', 'name')
@ -191,18 +293,15 @@ def history(request, object, id):
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
if not request.user.is_admin and object_instance != request.user:
if not request.user.has_perms(('perm',)) and object_instance != request.user:
messages.error(request, "Vous ne pouvez pas afficher l'historique d'un autre user que vous sans droit admin")
return redirect("/users/profil/" + str(request.user.id))
elif object == 'machines':
elif object == 'listright' and request.user.has_perms(('perm',)):
try:
object_instance = Machine.objects.get(pk=id)
except User.DoesNotExist:
messages.error(request, "Machine inexistante")
object_instance = ListRight.objects.get(pk=id)
except ListRight.DoesNotExist:
messages.error(request, "Droit inexistant")
return redirect("/users/")
if not request.user.is_admin and object_instance.proprio != request.user:
messages.error(request, "Vous ne pouvez pas afficher l'historique d'un autre user que vous sans droit admin")
return redirect("/users/profil/" + str(request.user.id))
else:
messages.error(request, "Objet inconnu")
return redirect("/users/")
@ -217,7 +316,7 @@ def history(request, object, id):
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
reversions = paginator.page(paginator.num_pages)
return render(request, 'portail_captif/history.html', {'reversions': reversions, 'object': object_instance})
return render(request, 'med/history.html', {'reversions': reversions, 'object': object_instance})
@login_required
def mon_profil(request):
@ -230,70 +329,21 @@ def profil(request, userid):
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
machines_list = Machine.objects.filter(proprio=users)
if not request.user.is_admin and users != request.user:
if not request.user.has_perms(('perm',)) and users != request.user:
messages.error(request, "Vous ne pouvez pas afficher un autre user que vous sans droit admin")
return redirect("/users/profil/" + str(request.user.id))
emprunts_list = Emprunt.objects.filter(user=users)
list_droits = Right.objects.filter(user=users)
return render(
request,
'users/profil.html',
{
'user': users,
'machines_list': machines_list,
'emprunts_list': emprunts_list,
'list_droits': list_droits,
}
)
def get_ip(request):
"""Returns the IP of the request, accounting for the possibility of being
behind a proxy.
"""
ip = request.META.get("HTTP_X_FORWARDED_FOR", None)
if ip:
# X_FORWARDED_FOR returns client1, proxy1, proxy2,...
ip = ip.split(", ")[0]
else:
ip = request.META.get("REMOTE_ADDR", "")
return ip
def capture_mac(request, users, verbose=True):
remote_ip = get_ip(request)
if ipaddress.ip_address(remote_ip) in ipaddress.ip_network(CAPTIVE_IP_RANGE):
mac_addr = mac_from_ip(remote_ip)
if mac_addr:
machine = Machine()
machine.proprio = users
machine.mac_address = str(mac_addr)
try:
with transaction.atomic(), reversion.create_revision():
machine.save()
reversion.set_comment("Enregistrement de la machine")
except:
if verbose:
messages.error(request, "Assurez-vous que la machine n'est pas déjà enregistrée")
else:
if verbose:
messages.error(request, "Impossible d'enregistrer la machine")
else:
if verbose:
messages.error(request, "Merci de vous connecter sur le réseau du portail captif pour capturer la machine (WiFi %s)" % CAPTIVE_WIFI)
def capture_mac_afterlogin(sender, user, request, **kwargs):
capture_mac(request, user, verbose=False)
# On récupère la mac après le login
user_logged_in.connect(capture_mac_afterlogin)
@login_required
def capture(request):
userid = str(request.user.id)
try:
users = User.objects.get(pk=userid)
except User.DoesNotExist:
messages.error(request, "Utilisateur inexistant")
return redirect("/users/")
capture_mac(request, users)
return redirect("/users/profil/" + str(users.id))
def reset_password(request):
userform = ResetPasswordForm(request.POST or None)
if userform.is_valid():