# TP3 - Générateur de mots de passe - Listes et fichiers
## Objectifs du TP
### Objectifs techniques

Cette 3e itération a pour objectif de développer la fonctionnalité de génération de passphrase de notre générateur de mots de passe (3e et 4e options du menu). Nous utiliserons pour cela le processus décrit dans le document [Dice-Generated Passphrase](https://www.eff.org/dice) fourni par l'Electronic Frontier Foundation.

### Objectifs pédagogiques

- **Cybersécurité** : Comprendre l'intérêt et l'usage des passphrases
- **Listes** : Manipuler les listes en Python
- **Fichiers** : Renforcer la maîtrise des fonctions

## Validation

### Livrables

- Livrez le code final en plus du compte-rendu du TP dans un répertoire avec le nom suivant `YYYY-MM-DD_TPX_NOM1_Prenom1_NOM2_Prenom2`.
- Le compte rendu peut-être livré au format Markdown ou PDF. 
- Si vous choisissez le Markdown, attention à bien livrer les éventuelles images et à la validité des liens.

### Résultats attendus

- [ ] Le programme se lance sans erreur.
- [ ] La fonction `generer_passphrase` est créée avec les bons paramètres et renvoie une chaîne de caractères (str).
- [ ] La fonction `get_word_list_from_file` est créée avec les bons paramètres et renvoie une liste de mots (list).
- [ ] La passphrase générée avec l'option 3 du menu contient 6 mots séparés par des tirets sur une seule ligne.
- [ ] La passphrase générée avec l'option 4 du menu contient un nombre de mots paramétrable via une interaction avec l'utilisateurice.
- [ ] Chaque fonction contient une docstring minimale expliquant sa fonctionnalité. Bonus : Doctsrings complètes au format Numpy.
- [ ] Le code est propre, correctement formaté, les variables, fonctions et paramètres sont bien nommés, les conventions sont respectées.

### Critères d'évaluation

- 50% Technique (10pts)
    - Code fonctionnel
    - Réponses dans le compte-rendu
    - Questions orale et compréhension générale
    - Connaissance du cours
- 50% Professionnalisme (10pts)
    - Posture professionnelle durant l'activité (3pts)
    - Qualité du rendu (Compte rendu - Documentation - Code) (3pts)
    - Respect des délais/horaires (2pts)
    - Autonomie dans la progression (2pts)
- Bonus +2pts

## Documents fournis

- Cours :
    - Python - 4 - Les structures de données : détaille le fonctionnement des structures de données en Python (listes, dictionnaires, tuples, sets)
    - Python - 5 - Les fichiers : explique les fondamentaux de la manipulation de fichiers en Python
- Documents :
    - [Dice-generated passphrases (en)](https://www.eff.org/dice) : Propose un processus pour générer des passphrases sécurisées et précise le raisonnement ainsi que le contexte d'utilisation recommandé
    - [Python - Documentation - Data structures](https://docs.python.org/3/tutorial/datastructures.html)
- Fichiers
    - `wordlist_en.txt` : liste de mots pour générer des passphrases aléatoires avec des mots anglais
    - `wordlist_fr.txt` : liste de mots pour générer des passphrases aléatoires avec des mots français
    - `bonus_wordlist_en.txt` : liste de mots pour générer des passphrases aléatoires avec des mots anglais (version pour le bonus)
    - `bonus_wordlist_fr.txt` : liste de mots pour générer des passphrases aléatoires avec des mots français (version pour le bonus)

## Étapes

### 0 - Etude documentaire

**Objectif :** Comprendre pourquoi et comment générer des passphrases

```admonish travail
- Lire le document de l'EFF [Dice-generated passphrases (en)](https://www.eff.org/dice)
```

```admonish note title="Dans le compte rendu"
1. Qu'est-ce que l'Electronic Frontier Foundation en quelques mots ?
2. Expliquez en quelques lignes le principe de la génération de passphrases selon le document
3. Dans quels cas l'EFF recommande d'utiliser les passphrases plutôt que des mots de passes aléatoires classiques ?
```

### 1 - Ouvrir un fichier pour lire la liste des mots

**Objectif :** Créer une fonction qui ira lire les mots dans un fichier et utiliser cette liste pour générer des passphrases en français

```admonish travail
1. Dans votre module `mdp`, créer une nouvelle fonction `get_word_list_from_file`. Cette fonction prendra un paramètre `filename` et la valeur par défaut `"wordlist_fr.txt"`.
1. Ouvrir le fichier à l'aide d'un **gestionnaire de contexte** (structure `with ... as ...:`)
1. Lire toutes les lignes du fichier, et les stocker sous forme de **liste** dans la variable `lines`. Retourner cette variable.
1. Dans une console Python, importer votre module `mdp` puis appeler la fonction `get_word_list_from_file`. Vérifier qu'elle retourne bien une liste de mots.
```

```admonish help title="Aide"
Un gestionnaire de contexte est la méthode recommandée par Python pour ouvrir et fermer un fichier en toute sécurité. La syntaxe est la suivante:
~~~python
with open(filename, mode) as file:
    # Actions sur le fichier
~~~

Cette syntaxe présente l'avantage de fermer systématiquement votre fichier à la sortie du bloc de contexte, et évite donc de nombreux risques de bugs pour votre application. Elle définit une variable `file` qui sera détruite une fois sorti du bloc de contexte.
```

```admonish note title="Dans le compte rendu"
1. Donnez le code de votre fonction et expliquer son fonctionnement
2. Si nous n'avions pas utilisé de gestionnaire de contexte (`with ... as ...:`), qu'aurait-on dû absolument faire après avoir travaillé avec notre fichier ? Quelle méthode aurions-nous dû utiliser pour ça ?
```

### 2 - Créer la fonction `generer_passphrase`

**Objectif :** Créer une fonction pour générer les passphrase et l'appeler dans le choix n°3 du menu

```admonish travail
1. Dans le module `mdp`, créer une nouvelle fonction `generer_passphrase` qui ne prend aucun paramètre.
2. Dans le corps de la fonction :
    - Créer une variable `words` qui sera une liste. Cette liste contiendra les mots "passphrase" "generee" "par" "defaut".
    - Créer une variable `passphrase` dans laquelle vous allez **concaténer** (c'est à dire mettre bout à bout) les éléments de votre liste `words`, grâce à la fonction `join`. Les mots devront être séparés par un tiret `-`.
    - Retournez votre nouvelle variable `passphrase`
    Ce sera temporaire, nous modifierons cette fonction ensuite.
3. Dans votre module principal (`main.py`), modifier le choix n°3 du menu pour **afficher** le résultat de la fonction `generer_passphrase` au lieu du print actuel.
4. Tester le programme, en choisissant le choix n°3 et en vérifiant que le programme vous affiche bien `passphrase-generee-par-defaut`
```

```admonish help title="Aide"
Syntaxe pour créer une liste et la stocker dans une variable :
~~~python
ma_liste = ["elements", "de", "ma", "liste"]
ma_liste_de_nombres = [1, 2, 3, 4]
~~~
```

```admonish note title="Dans le compte rendu"
1. Mettre le code de la fonction `generer_passphrase` et l'expliquer
2. Mettre le code de la fonction `choix_utilisateur` que vous avez modifiée
```

### 3 - Générer une phrase de passe aléatoire

**Objectif :** Générer une phrase de passe aléatoire à partir de la liste de mots lue à l'étape 1

```admonish travail
1. Dans votre module `mdp`, fonction `generer_passphrase`, remplacer la liste de mots fixe affectée à la variable `words` par un appel à la fonction `get_word_list_from_file` développée à l'étape 1
1. La liste contient des milliers de mots, il va donc falloir maintenant tirer au sort quelques mots pour créer notre passphrase. Comme dans le TP précédent, nous allons utiliser `secrets.choice`, mais cette fois au lieu de tirer des lettres dans `ALPHABET`, nous allons tirer des mots dans `words`.
    1. Créer une variable `passphrase_words` qui sera une liste vide, et qui nous servira à stocker les mots tirés au sort
    1. Créer une boucle qui bouclera 6 fois (avec `range`) (pour avoir 6 mots, ce qui est la recommandation minimale de l'EFF)
    2. Dans cette boucle, nous ajouterons à `passphrase_words` un mot tiré au sort dans `words`.
5. Modifier le calcul de la variable `passphrase` (celle dans laquelle on vient assembler les mots) pour utiliser `passphrase_words` au lieu de `words`
6. Tester le programme pour vérifier que nos passphrases sont bien générées aléatoirement
```

```admonish help title="Aide"
Syntaxe pour ajouter un élément dans une liste ([doc](https://docs.python.org/3/tutorial/datastructures.html)):
~~~python
ma_liste.append(mon_nouvel_element)
~~~
```

```admonish tip title="Remarque"
Les chaînes de caractères et les listes ont souvent des comportements comparables. En réalité, une chaîne de caractères est presque comme une liste de caractères. C'est pour cela que de nombreuses choses qui fonctionnent avec les listes marchent aussi avec les chaînes de caractères.

La fonction `secrets.choice` par exemple va tirer un élément au hasard dans un ensemble. Cela fonctionne aussi bien avec les listes que les chaînes de caractères.
```

```admonish note title="Dans le compte rendu"
1. Donnez le code de la fonction `generer_passphrase`
2. Expliquez en détail ce que fait la fonction
```

### 4 - Rendre paramétrable la longueur de la passphrase

**Objectif :** Modifier la fonction `generer_passphrase` pour y ajouter un paramètre permettant de choisir le nombre de mots

```admonish travail
1. Ajouter un paramètre `nb_words` dans la fonction `generer_passphrase`, qui aura pour valeur par défaut 6
2. Modifier la boucle pour qu'elle tire au sort `nb_words` mots.
3. Vérifier que votre choix n°3 fonctionne toujours sans autre modification
4. Modifier le choix n°4 pour :
    - Demander à l'utilisateur le nombre de mots souhaité
    - Appeler votre fonction `generer_passphrase` en lui passant en paramètre le nombre de mots demandé par l'utilisateur
```

```admonish note title="Dans le compte rendu"
1. Mettre le code de la fonction `generer_passphrase`
2. Expliquer ce à quoi sert de donner une valeur par défaut à un paramètre
```


## Pour aller plus loin

### Bonus facile - Docstrings au format Numpy

**Objectif**: Documenter votre code en ajoutant des docstrings au format Numpy


### Bonus intermédiaire - Complexifier les passphrases générées

**Objectif** : Tous les mots de la passphrase commencent par une majuscule et se terminent par un caractère spécial ou un chiffre

```admonish travail
1. Modifier les mots tirés au sort [avec la **méthode** `capitalize`](https://docs.python.org/3/library/stdtypes.html#str.capitalize) pour qu'ils commencent par une majuscule
2. Modifier les mots pour ajouter au bout de chaque mot un caractère spécial ou un chiffre tiré au sort avec `secrets.choice`
3. Rendre paramétrables ces deux possibilités dans le choix n°4 du menu (et les désactiver par défaut dans le choix n°3)
```

### Bonus long et difficile - Utiliser un dictionnaire et un jet de 5 dés

**Objectif** : Respecter rigoureusement la procédure de l'EFF en lançant des dés qui serviront à choisir les mots de la passphrase

```admonish travail
1. Dans votre module `mdp`, créer une fonction `generer_dice_passphrase` qui sera une copie de `generer_passphrase` (que nous allons modifier)
1. Créer une nouvelle fonction `get_word_dict_from_file`. Cette fonction prendra un paramètre `filename` et la valeur par défaut `"bonus_wordlist_fr.txt"`.
2. Ouvrir le fichier à l'aide d'un **gestionnaire de contexte**
3. À l'aide d'une boucle `for`, lire le fichier ligne par ligne et construire un dictionnaire. Chaque ligne est composée de la manière suivante : `12345 mot\n` (5 chiffres représentant le tirage de dés, un espace puis un mot, et enfin un retour à la ligne)
5.  Retournez votre dictionnaire
6.  Remplacer l'appel à la fonction `get_word_list_from_file` dans votre fonction `generer_dice_passphrase` par `get_word_dict_from_file`.

8. Créer une nouvelle fonction `tirer_les_des` dans votre module `mdp`.
9. Dans cette fonction, commencer par créer une variable `dice` qui sera une chaîne de caractères vide.
10. Créer une boucle qui fera 5 itérations. Dans cette boucle :
    - Tirer au sort un nombre entre 1 et 6. Vous utiliserez pour cela [`secrets.randbelow`](https://docs.python.org/3/library/secrets.html#secrets.randbelow).
    - Lisez la documentation de `secrets.randbelow` pour comprendre son fonctionnement
    - Ajouter ce nombre sous forme de chaîne de caractères à votre variable `dice`
11. A la fin de la boucle, retourner la valeur de `dice`
12. Vérifiez que votre fonction retourne une chaîne de caractères aléatoire ressemblant à `"12345"`
1. Modifier la boucle de `generer_dice_passphrase` pour 
    - appeler `tirer_les_des` et récupérer le jet de dés dans une variable `dice`
    - utiliser l'une des deux syntaxes possibles permettant de récupérer une valeur dans un dictionnaire à l'aide de la clé. Votre but est de récupérer le mot à partir du tirage de dés
    - Ajoutez ce mot à votre liste `passphrase_words`
6. Remplacez l'appel à `generer_passphrase` dans votre menu par un appel à `generer_dice_passphrase` et validez que votre programme fonctionne toujours correctement. Si tout fonctionne, bel exploit !
```

```admonish help title="Aide"
- `secrets.randbelow` retourne un nombre entier. Vous aurez besoin d'une chaine de caractères. Pensez à vos conversions si vous rencontrez l'erreur suivante :
  ~~~
  TypeError: can only concatenate str (not "int") to str
  ~~~
- `secrets.randbelow(n)` retourne un nombre compris entre `0` et `n-1`. Pour avoir un nombre compris entre `1` et `n`, il vous faudra ajouter 1 au résultat.
- Pour séparer une chaine de caractères, on peut utiliser la méthode [`split`](https://docs.python.org/3/library/stdtypes.html#str.split) sur la chaîne de caractères.
- Pour retirer les espaces et autres passages à la ligne autour des mots, on peut utiliser la méthode [`strip`](https://docs.python.org/3/library/stdtypes.html#str.strip)
- Pour construire votre dictionnaire, vous devrez boucler sur vos lignes, séparer les deux mots et les nettoyer. Puis vous pourrez les ajouter au dictionnaire avec la syntaxe `mon_dict[key] = value`. N'oubliez pas d'initialiser un dictionnaire vide (`{}`) avant votre boucle.
```

```admonish note title="Dans le compte rendu"
1. Mettre le code des trois fonctions écrites pour ce bonus
1. Expliquer le code écrit
```

### Ressources supplémentaires

- [Diceware Home Page](https://theworld.com/~reinhold/diceware.html)
- [Can I use a computer to generate Diceware Passphrases ?](https://theworld.com/%7Ereinhold/dicewarefaq.html#computer)
- [Liste de mots diceware en français](https://github.com/mbelivo/diceware-wordlists-fr/tree/master)
- [xkcd - Password Strength](https://xkcd.com/936/)
- [Deep Dive: EFF's New Wordlists for Random Passphrases](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases)