Commit 295e6f4f authored by Gabriel Detraz's avatar Gabriel Detraz

initial commit amap

parents
settings_local.py
*.swp
*.pyc
__pycache__
static_files/*
# Re2o
## Avant propos
Amap 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.
Il utilise le framework django avec python3. Il permet de gérer les adhérents, les machines, les factures, les droits d'accès, les switchs et la topologie du réseau.
De cette manière, il est possible de pluguer très facilement des services dessus, qui accèdent à la base de donnée en passant par django (ex : dhcp), en chargeant la liste de toutes les mac-ip, ou la liste des mac-ip autorisées sur le réseau (adhérent à jour de cotisation).
## Installation
Dépendances :
* python3-django (1.8, jessie-backports)
* python3-django-reversion (1.10, stretch)
* django-bootstrap3 (pip3 install)
* django-ldapdb (pip3 install)
Moteur de db conseillé (mysql), postgresql fonctionne également.
Pour mysql, il faut installer :
* mysql-server (jessie)
* python3-mysqldb (jessie-backports)
## Configuration
Le site est prêt a fonctionner, il faut simplement créer la base de donnée (par défamap), et régler les variables présentes dans setting_local.py
Un fichier d'exemple est disponible.
Ensuite, effectuer les migrations. Un squelette de base de donnée, via un mysqldump peut être fourni.
## Mise en production avec apache
amap/wsgi.py permet de fonctionner avec apache2 en production
## Fonctionnement avec les services
Pour charger les objets django, il suffit de faire User.objects.all() pour tous les users par exemple.
Cependant, pour que les services fonctionnent de manière simple, des fonctions toutes prètes existent deja pour charger la liste des users autorisés à se connecter ( has_access(user)), etc. Ces fonctions sont personnalisables, et permettent un fonctionnement très simple des services.
from .settings import SITE_NAME
from users.models import Right
def context_user(request):
user = request.user
is_cableur = user.has_perms(('cableur',))
is_bureau = user.has_perms(('bureau',))
is_bofh = user.has_perms(('bofh',))
is_trez = user.has_perms(('trésorier',))
is_infra = user.has_perms(('infra',))
if user.is_authenticated():
list_droits = Right.objects.filter(user=user)
else:
list_droits = None
return {
'request_user': user,
'is_cableur': is_cableur,
'is_bureau': is_bureau,
'is_bofh': is_bofh,
'is_trez': is_trez,
'is_infra': is_infra,
'list_droits': list_droits,
'site_name': SITE_NAME,
}
# -*- coding: utf-8 -*-
# Module d'authentification
# David Sinquin, Gabriel Détraz, Goulven Kermarec
import hashlib
import binascii
import os
from base64 import encodestring
from base64 import decodestring
from collections import OrderedDict
from django.contrib.auth import hashers
ALGO_NAME = "{SSHA}"
ALGO_LEN = len(ALGO_NAME + "$")
DIGEST_LEN = 20
def makeSecret(password):
salt = os.urandom(4)
h = hashlib.sha1(password.encode())
h.update(salt)
return ALGO_NAME + "$" + encodestring(h.digest() + salt).decode()[:-1]
def checkPassword(challenge_password, password):
challenge_bytes = decodestring(challenge_password[ALGO_LEN:].encode())
digest = challenge_bytes[:DIGEST_LEN]
salt = challenge_bytes[DIGEST_LEN:]
hr = hashlib.sha1(password.encode())
hr.update(salt)
valid_password = True
# La comparaison est volontairement en temps constant
# (pour éviter les timing-attacks)
for i, j in zip(digest, hr.digest()):
valid_password &= i == j
return valid_password
class SSHAPasswordHasher(hashers.BasePasswordHasher):
"""
SSHA password hashing to allow for LDAP auth compatibility
"""
algorithm = ALGO_NAME
def encode(self, password, salt, iterations=None):
"""
Hash and salt the given password using SSHA algorithm
salt is overridden
"""
assert password is not None
return makeSecret(password)
def verify(self, password, encoded):
"""
Check password against encoded using SSHA algorithm
"""
assert encoded.startswith(self.algorithm)
return checkPassword(encoded, password)
def safe_summary(self, encoded):
"""
Provides a safe summary ofthe password
"""
assert encoded.startswith(self.algorithm)
hash = encoded[ALGO_LEN:]
hash = binascii.hexlify(decodestring(hash.encode())).decode()
return OrderedDict([
('algorithm', self.algorithm),
('iterations', 0),
('salt', hashers.mask_hash(hash[2*DIGEST_LEN:], show=2)),
('hash', hashers.mask_hash(hash[:2*DIGEST_LEN])),
])
def harden_runtime(self, password, encoded):
"""
Method implemented to shut up BasePasswordHasher warning
As we are not using multiple iterations the method is pretty useless
"""
pass
"""Generated by 'django-admin startproject' using Django 1.8.13.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from .settings_local import SECRET_KEY, DATABASES, DEBUG, ALLOWED_HOSTS, ASSO_NAME, ASSO_ADDRESS_LINE1, ASSO_ADDRESS_LINE2, ASSO_SIRET, ASSO_EMAIL, ASSO_PHONE, LOGO_PATH, services_urls, REQ_EXPIRE_HRS, REQ_EXPIRE_STR, EMAIL_FROM, SITE_NAME
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# Auth definition
PASSWORD_HASHERS = (
'amap.login.SSHAPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
)
AUTH_USER_MODEL = 'users.User'
LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/'
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap3',
'users',
'amap',
'reversion'
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)
ROOT_URLCONF = 'amap.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates').replace('\\', '/'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
'amap.context_processors.context_user',
],
},
},
]
WSGI_APPLICATION = 'amap.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'fr-fr'
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# django-bootstrap3 config dictionnary
BOOTSTRAP3 = {
'jquery_url': '/static/js/jquery-2.2.4.min.js',
'base_url': '/static/bootstrap/',
'include_jquery': True,
}
BOOTSTRAP_BASE_URL = '/static/bootstrap/'
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(
BASE_DIR,
'static',
),
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static_files')
RIGHTS_LINK = {
'cableur' : ['bureau','infra','bofh','trésorier'],
'bofh' : ['bureau','trésorier'],
}
PAGINATION_NUMBER = 25
PAGINATION_LARGE_NUMBER = 8
SECRET_KEY = 'SUPER_SECRET'
DB_PASSWORD = 'SUPER_SECRET'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = []
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 're2o',
'USER': 're2o',
'PASSWORD': DB_PASSWORD,
'HOST': 'localhost',
}
}
# Association information
SITE_NAME = "Re2o.rez"
LOGO_PATH = "static_files/logo.png"
ASSO_NAME = "Asso reseau"
ASSO_ADDRESS_LINE1 = "2, rue Edouard Belin"
ASSO_ADDRESS_LINE2 = "57070 Metz"
ASSO_SIRET = ""
ASSO_EMAIL = "tresorier@ecole.fr"
ASSO_PHONE = "01 02 03 04 05"
services_urls = {
#Fill IT : ex : 'gitlab': {'url': 'https://gitlab.rezometz.org', 'logo': 'gitlab.png'},
}
# Number of hours a token remains valid after having been created. Numeric and string
# versions should have the same meaning.
REQ_EXPIRE_HRS = 48
REQ_EXPIRE_STR = '48 heures'
# Email `From` field
EMAIL_FROM = 'www-data@serveur.net'
{% if reversions.paginator %}
<ul class="pagination nav navbar-nav">
{% if reversions.has_previous %}
<li><a href="?page={{ reversions.previous_page_number }}">Suivants</a></li>
{% endif %}
{% for page in reversions.paginator.page_range %}
<li class="{% if reversions.number == page %}active{% endif %}"><a href="?page={{page }}">{{ page }}</a></li>
{% endfor %}
{% if reversions.has_next %}
<li> <a href="?page={{ reversions.next_page_number }}">Précédents</a></li>
{% endif %}
</ul>
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th>Date</th>
<th>Cableur</th>
<th>Commentaire</th>
</tr>
</thead>
{% for rev in reversions %}
<tr>
<td>{{ rev.revision.date_created }}</td>
<td>{{ rev.revision.user }}</td>
<td>{{ rev.revision.comment }}</td>
</tr>
{% endfor %}
</table>
{% extends "amap/sidebar.html" %}
{% load bootstrap3 %}
{% block title %}Historique{% endblock %}
{% block content %}
<h2>Historique de {{ object }}</h2>
{% include "amap/aff_history.html" with reversions=reversions %}
<br />
<br />
<br />
{% endblock %}
{% extends "amap/sidebar.html" %}
{% load bootstrap3 %}
{% load staticfiles %}
{% block title %}Accueil{% endblock %}
{% block content %}
<h1>Bienvenue sur {{ site_name }} !</h1>
<div class="row">
{% if services_urls.zerobin %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.zerobin.logo %}" alt="zerobin">
<div class="caption">
<h3>Zerobin</h3>
<p>Le zerobin, un service pour partager un contenu avec un lien. Copier-coller, envoyer ! Le lien peut être à usage unique</p>
<p><a href="{{ services_urls.zerobin.url }}" class="btn btn-primary" role="button">Accéder au zerobin</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.wiki %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.wiki.logo %}" alt="wiki">
<div class="caption">
<h3>Wiki</h3>
<p>Le wiki, toutes les informations et les données techniques à propos de la structure du réseau et de sa gestion,
ainsi que les personnes qui le gèrent</p>
<p><a href="{{ services_urls.wiki.url }}" class="btn btn-primary" role="button">Accéder au wiki</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.gitlab %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.gitlab.logo %}" alt="gitlab">
<div class="caption">
<h3>Gitlab</h3>
<p>Découvrez le gitlab hébérgé par nos soins ! Idéal pour vos projets personnels, ou pour des projets collaboratif !
Accès avec votre compte</p>
<p><a href="{{ services_urls.gitlab.url }}" class="btn btn-primary" role="button">Accéder au gitlab</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.kanboard %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.kanboard.logo %}" alt="kanboard">
<div class="caption">
<h3>Kanboard</h3>
<p>Découvrez kanboard, pour gérer un projet complexe et le diviser en minitaches. Conçu pour travailler en groupe sur
des projets, accessible avec votre compte</p>
<p><a href="{{ services_urls.kanboard.url }}" class="btn btn-primary" role="button">Accéder au kanboard</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.etherpad %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.etherpad.logo %}" alt="etherpad">
<div class="caption">
<h3>Etherpad</h3>
<p>Etherpad, un pad collaboratif, ouvert, editable par tous ! Un editeur de texte libre qui permet une édition collaborative
avec un chat</p>
<p><a href="{{ services_urls.etherpad.url }}" class="btn btn-primary" role="button">Accéder au pad</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.planner %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<div class="caption">
<h3>Planner</h3>
<p>Planner, un service pour planifier les événements à plusieurs, et choisir une date commune. Version open source
mise en place par framasoft</p>
<p><a href="{{ services_urls.planner.url }}" class="btn btn-primary" role="button">Accéder au planner</a></p>
</div>
</div>
</div>
{% endif %}
{% if services_urls.federez %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{% static "logo/"|add:services_urls.federez.logo %}" alt="federez">
<div class="caption">
<h3>FedeRez</h3>
<p>La fédération française des associations réseaux étudiants des grandes écoles et université, FedeRez, vous propose
de découvrir les nombreux services hébergés par ses membres</p>
<p><a href="{{ services_urls.federez.url }}" class="btn btn-primary" role="button">Accéder au site de FedeRez</a></p>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% extends "base.html" %}
{% block sidebar %}
{% endblock %}
"""amap URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.8/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views as auth_views
from .views import index
urlpatterns = [
url(r'^$', index),
url('^logout/', auth_views.logout, {'next_page': '/'}),
url('^', include('django.contrib.auth.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^users/', include('users.urls', namespace='users')),
#url(r'^logs/', include('logs.urls', namespace='logs')),
]
from django.shortcuts import render
from django.shortcuts import render_to_response, get_object_or_404
from django.core.context_processors import csrf
from django.template import Context, RequestContext, loader
from amap.settings import services_urls
def form(ctx, template, request):
c = ctx
c.update(csrf(request))
return render_to_response(template, c, context_instance=RequestContext(request))
def index(request):
return form({'services_urls': services_urls}, 'amap/index.html', request)
"""
WSGI config for amap project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
from os.path import dirname
import sys
sys.path.append(dirname(dirname(__file__)))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "amap.settings")
application = get_wsgi_application()
#!/usr/bin/env python3
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "amap.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
from django.contrib import admin
# Register your models here.
from django.db.models import Q
from simple_search import BaseSearchForm
from users.models import User, School
class UserSearchForm(BaseSearchForm):
class Meta:
base_qs = User.objects
search_fields = ('^name', 'description', 'specifications', '=id')
# assumes a fulltext index has been defined on the fields
# 'name,description,specifications,id'
fulltext_indexes = (
('name', 2), # name matches are weighted higher
('name,description,specifications,id', 1),
)
from django.db import models
from django import forms
from django.forms import Form
from django.forms import ModelForm
CHOICES = (
('0', 'Actifs'),
('1', 'Désactivés'),
('2', 'Archivés'),
)
CHOICES2 = (
(1, 'Active'),
("", 'Désactivée'),
)
CHOICES3 = (
('0', 'Utilisateurs'),
('1', 'Machines'),
('2', 'Factures'),
('3', 'Bannissements'),
('4', 'Accès à titre gracieux'),
('6', 'Switchs'),
('5', 'Ports'),
)
class SearchForm(Form):
search_field = forms.CharField(label = 'Search', max_length = 100)
class SearchFormPlus(Form):
search_field = forms.CharField(label = 'Search', max_length = 100, required=False)
filtre = forms.MultipleChoiceField(label="Filtre utilisateurs", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES)
connexion = forms.MultipleChoiceField(label="Filtre connexion", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES2)
affichage = forms.MultipleChoiceField(label="Filtre affichage", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES3)
date_deb = forms.DateField(required=False, label="Date de début", help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'])
date_fin = forms.DateField(required=False, help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'], label="Date de fin")
{% extends "search/sidebar.html" %}
{% load bootstrap3 %}
{% block title %}Résultats de la recherche{% endblock %}
{% block content %}
{% if users_list %}
<h2>Résultats dans les utilisateurs</h2>
{% include "users/aff_users.html" with users_list=users_list %}
{% endif%}
{% if interfaces_list %}
<h2>Résultats dans les machines : </h2>
{% include "machines/aff_machines.html" with interfaces_list=interfaces_list %}
{% endif %}
{% if facture_list %}
<h2>Résultats dans les factures : </h2>
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
{% endif %}
{% if white_list %}
<h2>Résultats dans les accès à titre gracieux : </h2>
{% include "users/aff_whitelists.html" with white_list=white_list %}
{% endif %}
{% if ban_list %}
<h2>Résultats dans les banissements : </h2>
{% include "users/aff_bans.html" with ban_list=ban_list %}
{% endif %}
{% if switch_list %}
<h2>Résultats dans les switchs : </h2>
{% include "topologie/aff_switch.html" with switch_list=switch_list %}
{% endif %}
{% if port_list %}
<h2>Résultats dans les ports : </h2>
{% include "topologie/aff_port.html" with port_list=port_list %}
{% endif %}
{% if not ban_list and not interfaces_list and not users_list and not facture_list and not white_list and not port_list and not switch_list%}
<h3>Aucun résultat</h3>
{% endif %}
<br />
<br />
<br />
{% endblock %}
{% extends "search/sidebar.html" %}
{% load bootstrap3 %}
{% block title %}Recherche{% endblock %}
{% block content %}
{% bootstrap_form_errors searchform %}
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form searchform %}
{% bootstrap_button "Search" button_type="submit" icon="search" %}
</form>
<br />
<br />
<br />
<br />
<br />
{% endblock %}
{% extends "base.html" %}
{% block sidebar %}
<p><a href="{% url "search:search" %}">Recherche simple</a></p>
<p><a href="{% url "search:searchp" %}">Recherche avancée</a></p>
{% endblock %}
from django.test import TestCase
# Create your tests here.
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.search, name='search'),
url(r'^avance/$', views.searchp, name='searchp'),
]
# App de recherche pour amap
# Augustin lemesle, Gabriel Détraz, Goulven Kermarec
# Gplv2
from django.shortcuts import render
from django.shortcuts import render_to_response, get_object_or_404
from django.core.context_processors import csrf
from django.template import Context, RequestContext, loader
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from users.models import User, Ban, Whitelist
from machines.models import Machine, Interface
from topologie.models import Port, Switch
from cotisations.models import Facture
from search.models import SearchForm, SearchFormPlus