Première implementation de sso

This commit is contained in:
2026-08-03 13:05:09 +00:00
commit a494199820
31 changed files with 440 additions and 0 deletions

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
authentification/admin.py Normal file
View File

@@ -0,0 +1,12 @@
from django.contrib import admin
from authentification.models import Departement, Employe
@admin.register(Departement)
class DepartementAdmin(admin.ModelAdmin):
list_display = ('nom',)
@admin.register(Employe)
class EmployeAdmin(admin.ModelAdmin):
list_display = ('user', 'matricule', 'departement', 'fonction', 'date_embauche')
list_filter = ('departement', 'fonction')
search_fields = ('user__username', 'matricule')

5
authentification/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class AuthentificationConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'authentification'

View File

@@ -0,0 +1,49 @@
from django.contrib.auth.models import User
from django.db import models
class Departement(models.Model):
"""Modèle représentant un département de l'entreprise."""
nom = models.CharField(max_length=100)
def __str__(self):
return self.nom
class Employe(models.Model):
"""Modèle représentant un employé de l'entreprise."""
FONCTION_LISTE = [
('directeur', 'Directeur'),
('assistant_direction', 'Assistante de direction'),
('assistant_technique_recherche', 'Assistant technique de recherche'),
('comptable', 'Comptable'),
('raf', 'RAF'),
('data_manager', 'Data Manager'),
('logisticien', 'Logisticien'),
('post_doctorant', 'Post-Doctorant'),
('qualiticien', 'Qualiticien'),
('technicien_surface', 'Technicien de surface'),
('chauffeur', 'Chauffeur'),
]
user = models.OneToOneField(User, on_delete=models.CASCADE)
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
departement = models.ForeignKey(Departement, on_delete=models.SET_NULL, null=True, blank=True)
fonction = models.CharField(max_length=50, blank=True, null=True, choices=FONCTION_LISTE)
date_embauche = models.DateField(blank=True, null=True)
adresse = models.CharField(max_length=100, null=True, blank=True)
telephone = models.CharField(max_length=15, null=True, blank=True)
sexe = models.CharField(max_length=1, null=True, blank=True, choices=[('m', 'Masculin'), ('f', 'Féminin')])
date_naissance = models.DateField(blank=True, null=True)
CV = models.FileField(upload_to='cv/', blank=True, null=True)
diplome = models.FileField(upload_to='diplomes/', blank=True, null=True)
rib = models.FileField(upload_to='rib/', blank=True, null=True)
photo = models.ImageField(upload_to='photos/', blank=True, null=True)
casier_judiciaire = models.FileField(upload_to='casier/', blank=True, null=True)
chef = models.BooleanField(
default=False,
verbose_name="Cet utilisateur est-il chef de ce département ?"
)
def __str__(self):
return f"{self.user.first_name or 'N/A'} {self.user.last_name or ' '} ({self.matricule or ' '})"

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -0,0 +1,44 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<title>Login - SIRH</title>
</head>
<body>
<div class="container-fluid vh-100">
<div class="row">
<div class="col-6 vh-100 d-flex flex-column justify-content-center align-items-center">
<img src="{% static 'images/cerfig.jpg' %}" class="w-50">
<h5 class="text-center">Bienvenue sur les systèmes de gestion du CERFIG</h5>
</div>
<div class="col-6 vh-100 d-flex justify-content-center align-items-center">
<form method="POST" action="{% url 'login' %}" class="w-100 shadow rounded px-3 py-5">
<h2 class="text-center">Connexion</h2>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
{% endfor %}
{% endif %}
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.GET.next }}">
<div class="mb-3">
<label for="mail">Votre adresse email :</label>
<input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required>
</div>
<div class="mb-3">
<label for="mot_de_passe">Mot de passe</label>
<input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required>
<i class="bi bi-eye toggle-password" onclick="togglePassword()"
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
</div>
<button type="submit" class="btn btn-primary w-100">Se connecter</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
authentification/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
from .views import login_view, deconnexion_view
urlpatterns = [
path('login/', login_view, name='login'),
path('deconnexion/', deconnexion_view, name='deconnexion'),
]

71
authentification/views.py Normal file
View File

@@ -0,0 +1,71 @@
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
# from django.contrib import messages
# from django.views.decorators.cache import never_cache
# from django.views.decorators.http import require_http_methods
# from django.contrib.auth import authenticate, login
# from django.shortcuts import render, redirect
def login_view(request):
if request.method == 'POST':
username = request.POST.get('mail')
password = request.POST.get('mot_de_passe')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
next_url = request.GET.get('next') or request.POST.get('next') or '/'
return redirect(next_url)
else:
return render(request, 'authentification/login.html', {'error': 'Identifiants invalides'})
return render(request, 'authentification/login.html')
# @never_cache
# # @require_http_methods(["GET", "POST"])
# def login_view(request):
# """
# Gère la connexion des utilisateurs avec redirection selon le rôle et
# vérification de l'acceptation de la politique d'utilisation.
# """
# # if request.user.is_authenticated:
# # next_url = request.GET.get('next', '')
# # if next_url:
# # return redirect(next_url)
# # return redirect("gestion_conges:conge")
# if request.method == 'POST':
# email = request.POST.get('mail')
# password = request.POST.get('mot_de_passe')
# # next_url = request.POST.get('next', '')
# if not (email and password):
# messages.error(request, "Veuillez remplir tous les champs.")
# return render(request, 'authentification/login.html')
# user = authenticate(request, username=email, password=password)
# if user is None or not user.is_active:
# messages.error(request, "Nom dutilisateur ou mot de passe incorrect ou le compte est inactif.")
# return render(request, 'authentification/login.html')
# login(request, user)
# # if next_url:
# # return redirect(next_url)
# # return redirect("gestion_conges:conge")
# return render(
# request,
# 'authentification/login.html',
# {
# 'next': request.GET.get('next', ''),
# }
# )
def deconnexion_view(request):
"""Gère la déconnexion de l'utilisateur."""
logout(request)
return redirect('login')

View File

View File

@@ -0,0 +1,16 @@
"""
ASGI config for cerfig_sso_authentification project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cerfig_sso_authentification.settings')
application = get_asgi_application()

View File

@@ -0,0 +1,131 @@
"""
Django settings for cerfig_sso_authentification project.
Generated by 'django-admin startproject' using Django 5.2.10.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config("DEBUG", default=True, cast=bool)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default=[]).split(",")
CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
LOGIN_URL = 'login/'
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'authentification',
'simple_sso.sso_server'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'cerfig_sso_authentification.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'cerfig_sso_authentification.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config("DB_NAME"),
'USER': config("DB_USER"),
'PASSWORD': config("DB_PASSWORD"),
'HOST': config("DB_HOST"),
'PORT': config("DB_PORT"),
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@@ -0,0 +1,15 @@
from simple_sso.sso_server.server import Server
from authentification.models import Employe
class CerfigSSOServer(Server):
def get_user_data(self, user, consumer, extra_data=None):
user_data = super().get_user_data(user, consumer, extra_data)
employe = Employe.objects.get(user=user)
user_data.update({
'departement': employe.departement.nom,
'chef_departement': employe.chef,
'groups': [group.name for group in user.groups.all()],
})
return user_data

View File

@@ -0,0 +1,31 @@
"""
URL configuration for cerfig_sso_authentification project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from cerfig_sso_authentification.sso import CerfigSSOServer
server_sso = CerfigSSOServer()
urlpatterns = [
path('', include('authentification.urls')),
path('admin/', admin.site.urls),
path(
'server/',
include(server_sso.get_urls())
)
]

View File

@@ -0,0 +1,16 @@
"""
WSGI config for cerfig_sso_authentification 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/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cerfig_sso_authentification.settings')
application = get_wsgi_application()

BIN
db.sqlite3 Normal file

Binary file not shown.

BIN
db.sqlite3:Zone.Identifier Normal file

Binary file not shown.

22
manage.py Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cerfig_sso_authentification.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

12
requirements.txt Normal file
View File

@@ -0,0 +1,12 @@
asgiref==3.11.1
certifi==2026.5.20
charset-normalizer==3.4.7
Django==5.2.15
django-simple-sso==1.3.0
idna==3.18
itsdangerous==0.24
pillow==12.2.0
requests==2.34.2
sqlparse==0.5.5
typing_extensions==4.15.0
urllib3==2.7.0