DAO et Sécurité
Le plan
- Data Access Object
- Pourquoi persister des données ?
- C’est quoi une DAO
- Exemple de DAO
- Sécurité Informatique
- Définition
- Failles de sécurité
- Injection SQL
- Gestion des mots de passe
- Authentification avec cypher
- Hashage de mots de passe
- Exemple
Nous allons commencer par parler Stockage des données
Data access object (DAO)
Notre objectif va être de stocker des données, mais avant :
Un ordinateur
Quels sont ses composants (en gros) ?
- Un processeur (CPU) : fait UNIQUEMENT du calcul
- La mémoire RAM : mémoire volatile rapide
- Disque dur (HDD, SSD) : mémoire longue durée
- Carte graphique : unité de calcul spécialisée
Bien insister sur CPU, RAM, disque !
Rendent 3 services différents :
- CPU : processeur
- DD : disque
- RAM : barrettes
Une question à se poser
= Personnage(prenom="Leia", nom="Organa", age = 35) p
C’est quoi une variable Python ?
- Une référence (le nom de la variable)
- Un objet associé (sa valeur)
On zappe la notion d’adressage mémoire.
Pas besoin de stocker la référence, car elle est dans le code source
Une question qui en découle
personnage.py
class Personnage:
def __init__(self, prenom, nom, age):
self.prenom = prenom
self.nom = nom
self.age = age
self.vaisseaux = []
def anniversaire(self):
self.age += 1
C’est quoi un objet Python ?
- Des attributs (qui peuvent être eux-mêmes des objets)
- Des méthodes
Pareil, les méthodes c’est du code, nous on veut juste les attributs
Pour résumer
- Nous voulons sauvegarder des couples clef-valeur
- Avec des valeurs qui peuvent être elles-mêmes constituées de couples clef-valeur
Besoin d’un dessin avec Leia et Vaisseaux
Parallèle avec les dict Python / JSON
Arbre car les attributs d’objets sont parfois eux-mêmes des objets
Comment faire cela ?
- Écrire nos données sur le disque dur dans un fichier
- csv, parquet, json, xml
- Utiliser une base de données
- SQL tables + relations, NoSQL
Question : Comment fait une BDD ?
- Elle gère dans des fichiers
- On peut faire le parallèle avec les couches
- La BDD rend un service, j’ignore comment elle fait
- Je sais ce qu’elle attend en entrée et ce qu’elle me donne en sortie !
Lien persistance / application
- Python : Variables volatiles en RAM
- Système de persistance : Données stockées sur disque
- Data Access Objects (DAO) en guise de lien
Petite pause récap pour essayer de récupérer tout le monde avant la DAO.
Dans la vraie vie, on ne repart pas de 0 quand on relance une appli.
Si vous créez un Personnage, la fois suivante il sera toujours là
C’est quoi une DAO ?
On vient de dire que l’on va stocker dans une BDD.
Comment y accéder ?
C’est quoi une DAO ?
- Classe technique
- Une classe DAO par objet métier
- Expose des méthodes pour communiquer avec la couche de persistance
C’est une API
Rend un service à des clients
Comment elle fait ? -> osef
Comme vous au restaurant. Vous voulez manger, comment c’est fait vous importe peu tant que le service est rendu (avec un niveau d’exigence)
Des ORM tels SQLAlchemy existent pour faire le lien Objet Python - Table BDD -> pas le sujet ici
Quelles méthodes exposer ?
- Create
- Read
- Update
- Delete
Faire participer
Essayer de montrer que ces 4 là suffisent.
Après on peut avoir des raffinements en faisant des choses en masse, ou filtrer, etc.
Quid de la copie ?
Une copie c’est une lecture + création donc on sait faire
Exemple dessin : classe Personne - table personne - dao personneDAO
L’intérêt d’une classe à part
Séparation des responsabilités
- Classe “jetable” 🚮
- Modifiable sans risque 🔨
- Parallélisation du travail 🦸♀️🧙♂️👨💼👩🔬
On remet une couche, ça fait pas de mal
Dans la vraie vie, si changement de système, plus facile de migrer
En projet, la couche DAO peut être très facile à faire
Petit recap
Petit exemple pour illustrer.
Exemple : lister toutes les personnes fans de Pokemon
Comment se connecter à une DB en python ?
Utilisation d’une bibliothèque dédiée
- PostgreSQL : psycopg2
- MySQL : mysql-connector-python
- Oracle : cx_Oracle
- MongoDB : pymongo
Vous lirez en entier la doc : psycopg2.
Il y aura des questions à l’exam !
psycopg2
pip install psycopg2-binary
connection
: permet d’établir la connexion avec la base
Pas super intéressant à faire, le code est donné
psycopg2 - connection
db_connection.py
import os
import dotenv
import psycopg2
from psycopg2.extras import RealDictCursor
from utils.singleton import Singleton
class DBConnection(metaclass=Singleton):
"""
Classe de connexion à la base de données
Elle permet de n'ouvrir qu'une seule et unique connexion
"""
def __init__(self):
"""Ouverture de la connexion"""
dotenv.load_dotenv()
self.__connection = psycopg2.connect(
=os.environ["POSTGRES_HOST"],
host=os.environ["POSTGRES_PORT"],
port=os.environ["POSTGRES_DATABASE"],
database=os.environ["POSTGRES_USER"],
user=os.environ["POSTGRES_PASSWORD"],
password=f"-c search_path={os.environ['POSTGRES_SCHEMA']}",
options=RealDictCursor,
cursor_factory
)
@property
def connection(self):
return self.__connection
Singleton
psycopg2 - cursor
cursor
: encapsule la requêtefrom dao.db_connection import DBConnection with DBConnection().connection as connection: with connection.cursor() as cursor:
cursor.execute("<une requête SQL>")
: permet de faire une requêtecursor.fetchone()/fetchall()/fetchmany()
: Récupération des résultats
Forme de base
def dao_function(self, arg1, arg2, ...):
# Récupération de la connexion à la base
with DBConnection().connection as connection:
# Création d'un curseur pour faire une requête
with connection.cursor() as cursor:
# On envoie au serveur la requête SQL
cursor.execute("<une_requete_sql_à_trous>",
remplisage_des_trous)
# On récupère le résultat de la requête
= cursor.fetchone() # ou fetchall()/fetchmany()
res
# Si la requête renvoie quelque chose
if res:
= "<res mis en forme>"
something
return something
Pourquoi on ne retourne pas directement res ?
res : list[dict]
On préfère retourner un Objet ou une liste[Objet]
Un petit exemple : LivreDao
ISBN : International Standard Book Number
Créer un livre
def create(self, livre) -> Livre:
"""Pour créer un livre en base"""
with DBConnection().connection as conn:
with conn.cursor() as cursor:
cursor.execute("INSERT INTO livre (isbm, titre, auteur) "
" VALUES (%(isbm)s, %(titre)s, %(auteur)s) "
" RETURNING id_livre; ",
"isbm": livre.isbm,
{"titre": livre.titre,
"auteur": livre.auteur},
)id = cursor.fetchone()["id_livre"]
livre.return livre
Possible aussi de retourner une booléen :
- true si création réussie
- false sinon
Lister les livres
def find_all(self) -> list[Livre]:
"""Pour récupérer tous les livres en base"""
with DBConnection().connection as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT id_livre, "
" isbm, "
" titre, "
" auteur "
" FROM livre ; "
)= cursor.fetchall()
livre_bdd
= []
liste_livres
if livre_bdd:
for livre in livre_bdd:
liste_livres.append(
Livre(id=livre["id_livre"],
=livre["isbm"],
isbm=livre["titre"],
titre=livre["auteur"],
auteur
)
)
return liste_livres
- livre_bdd = [{id_livre=1, isbm=123, “Le Schtroumpf hackeur”, “Peyo”}, {…}, …]
- livre = {id_livre=1, isbm=123, “Le Schtroumpf hackeur”, “Peyo”}
- liste_livres = [Livre(1, 123, “Le Schtroumpf hackeur”, “Peyo”), Livre(…), …]
Trouver un livre
def find_by_isbm(self, isbm) -> Livre:
"""Pour récupérer un livre depuis son isbm"""
with DBConnection().connection as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT * "
" FROM livre "
" WHERE isbm = %(isbm)s ",
"isbm": isbm}
{= cursor.fetchone()
livre_bdd
= None
livre if livre_bdd:
= Livre(
livre id=livre_bdd["id_livre"],
=livre_bdd["isbm"],
isbm=livre_bdd["titre"],
titre=livre_bdd["auteur"],
auteur
)return livre
livre_bdd : dict livre : Livre
Principe à retenir si vous voulez comprendre : nous allons toujours dans les DAO transformer les dict en objets
Conclusion
- Python travaille en RAM (volatile)
- Obligation d’avoir un mécanisme de persistance des données
- DAO : centralise les méthodes pour lire/écrire nos données
- La couche métier appelle la DAO sans se préoccuper du système de persistance
- Permet un travail d’équipe efficace 🦸♀️🧙♂️👨💼👩🔬
Sécurité informatique
Principes CAID
4 piliers de la sécurité info
- Confidentialité
- Authentification
- Intégrité
- Disponibilité
On utilise aussi parfois DICP ou DICPA
Disponibilité, Intégrité, Confidentialité, Preuve
Deux bonus
- Traçabilité
- Non-répudiation
Confidentialité
Mécanismes associés :
- gestion des droits (annuaires, rôles …)
- cryptographie
On parle plutôt de chiffrement
Authentification
Mécanismes associés :
- authentification faible (identifiant, mot de passe)
- authentification forte (données biométriques, multi-facteurs)
Intégrité
Mécanismes associés :
- signature électronique
- checksum
Signature électronique : garantir l’intégrité d’un document, c’est-à-dire s’assurer que le document n’a pas été altéré entre sa signature et sa consultation; authentifier son auteur, c’est-à-dire s’assurer de l’identité de la personne signataire.
Checksum : bit de parité.
Disponibilité
Mécanismes associés :
- redondance des serveurs
- virtualisation
- conteneurisation
Backup
Parler de la mise en production avant.
Traçabilité
Mécanisme associé :
- journalisation
Logs (mieux que les prints)
Projet 2024-2025
La non-répudiation
Mécanismes associés :
- traçabilité
- authentification
- intégrité
C’est pas moi
Mais c’est votre nom !
Ne laissez pas vos sessions ouvertes aux pauses (CTRL + ALT + F12)
Les failles informatiques
Trop de failles !!!
- Failles physiques “bas niveau”
- Failles physiques “haut niveau”
- Injection SQL
- Injection de données
- Faille XSS
- Exécution de code
- …
- Failles physiques “bas niveau” : coupure électrique, inondation salle serveur
- Failles physiques “haut niveau” : accès physique à une machine
De quoi faut-il se méfier ?
De vos utilisateurs
Exemple de failles, les injections SQL
-
Source : https://xkcd.com/
Injection SQL
Consiste à saisir du SQL pour exécuter une autre requête que celle prévue.
Problèmes :
- Confidentialité
- Authentification
- Intégrité
- Disponibilité
Exemple : s’authentifier sans mot de passe
Requête d’authentification
SELECT *
FROM user
WHERE name = 'input_name'
AND mdp = 'input_mdp';
- Gennysson
- awsome_password
SELECT *
FROM user
WHERE name = 'Gennysson'
AND mdp = 'awsome_password';
Si dans votre cursor.execute vous avez ce code
Exemple : s’authentifier sans mot de passe
SELECT *
FROM user
WHERE name = 'input_name'
AND mdp = 'input_mdp';
- Gennysson
' OR 1=1; --
SELECT *
FROM user
WHERE name = 'Gennysson'
AND mdp = '' OR 1=1; --';
Exemple : supprimer une table
SELECT *
FROM user
WHERE name = 'input_name'
AND mdp = 'input_mdp';
- Gennysson
'; DROP TABLE user CASCADE; --
SELECT *
FROM user
WHERE name='Gennysson'
AND mdp=''; DROP TABLE user CASCADE; --;
Comment se protéger ?
- Échapper les caractères spéciaux
- Utiliser une requête préparée
La bibliothèque que vous utiliserez ne fait que de l’échappement de caractères spéciaux 😨
La seconde solution est mieux que la première mais psycopg2 ne semble pas la proposer simplement.
Donc il faut la connaître, mais savoir que c’est la première solution qui va être faite.
Échapper les caractères spéciaux : par exemple, « ’ » sera remplacé par « ' »
L’apostrophe ne sera donc pas interprétée comme une fin de chaîne par le SGBD
Instruction “prepare” :
1. Envoie la requête à trous à la BDD
2. Envoie pour remplir les trous
Cross Site Scripting
Consiste à injecter du code provoquant des actions sur le navigateur. Cela peut permettre :
- Des redirections de page (phishing)
- Du vol d’information
- Des actions sur le site
- Rendre le site difficile à utiliser
Comment se protéger ?
- Ne jamais insérer des données brutes
- Échapper les caractères spéciaux
- Vérifier vos données
Les bibliothèques web le font souvent pour vous !
To sum up : injection
- Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs
- Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs
- Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs
Gestion des mots de passe
Votre application doit-elle stocker des mots de passe en clair?
Votre application doit-elle connaître le mot de passe d’un utilisateur pour l’authentifier ?
Comment on fait ?
Hasher le mot de passe
- Hashage du mot de passe
- Stockage du hash en base
- Quand besoin de comparer on hashe le mdp saisi
- Et on compare les hashs
Authentification sans persister les mots de passe !!!!
C’est tellement simple et sécurisé !
Mais pas tous les sites le font …
Un site qui vous renvoie votre mdp le garde en mémoire (ou du moins le moyen de le déchiffrer)
Dans la vraie vie, on rajoute un “sel” au mdp avant de le hacher.
C’est une valeur aléatoire calculée pour chaque utilisateur que l’on va rajouter au mdp avant de le hacher
Cela rend une attaque en force brute plus coûteuse.
Ajouter du sel pour plus de sécurité
Une base sans sel ajouté
- Votre base mail/mdp fuite mais les mdp sont hachés
- Les attaquants doivent bruteforce les mdp
- Ils commencent par les mdp les plus courants, et les hachent avec les algo de hash courants
- Puis ils comparent avec la base
- Forte chance d’avoir plusieurs correspondances
bruteforce : tester toutes les combinaisons possibles
Le sel c’est bon pour la sécurité
Au lieu de hacher et stocker le mdp vous stockez et hachez le mdp ET un élément lié à l’utilisateur de manière déterministe (le sel).
Maintenant même si 2 personnes ont le même mdp, elles auront des hash différents.
Le sel c’est bon pour la sécurité
- Votre base mail/mdp fuite mais les mdp sont hachés et salés
- Les attaquants doivent bruteforce les mdp
- Ils commencent par les mdp les plus courants, et les hachent avec les algo de hash courants
- Puis ils comparent avec la base.
- Il y a pas de match car vos mdp sont salés
- Et trouver un mdp ne permet de trouver les autres
Exemple de hashage de mdp
import hashlib
def hash_password(password, idep):
= idep
salt return hashlib.sha256(salt.encode() + password.encode()).hexdigest()
print(hash_password("awsome_password", "Gennysson"))
To sum up the security part
- Toujours vérifier les inputs
- Ne jamais faire confiance aux utilisateurs
- Plusieurs niveaux de sécurité
- Pas besoin de stocker les mots de passe en clair
Comment résoudre ce problème ?
Quel problème ??
Réponse : Conserver les données une fois le programme Python terminé