# TP4 - Générateur de mots de passe - Fichiers

## Objectifs du TP

### Objectifs pédagogiques

- **Fichiers** : Comprendre comment manipuler des fichiers en Python (lecture et écriture)
- **Dictionnaires** : Manipuler des dictionnaires en Python

### Objectifs techniques

- Écrire des mots de passe générés dans un fichier au lieu de les afficher en console
- Récupérer la liste des mots pour générer des passphrases directement depuis un fichier

## Validation

### Livrables

- Le code final 
- Le compte-rendu du TP

Le tout 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 `get_word_list_from_file` retourne une liste de mots tirée du fichier `wordlist_fr.txt`
- [ ] La fonction `write_password_in_file` écrit le mot de passe généré à la fin d'un fichier sans écraser son contenu précédent
- [ ] La fonction `tirer_les_des` tire cinq valeurs aléatoires entre 1 et 6 et les concatène dans une chaîne de caractères qu'elle retourne
- [ ] La fonction `generer_dice_passphrase` génère une passphrase aléatoire à l'aide d'un tirage de dés et la retourne
- [ ] 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 - 5 - Les fichiers
- Docs
  - Listes de mots en anglais
  - Listes de mots en français
  - Documentation de [`secrets.randbelow`](https://docs.python.org/3/library/secrets.html#secrets.randbelow), [`str.split`](https://docs.python.org/3/library/stdtypes.html#str.split), [`str.strip`](https://docs.python.org/3/library/stdtypes.html#str.strip)

## Étapes

### 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. Remplacer l'appel à la fonction `get_word_list` dans votre fonction `generer_passphrase` par votre nouvelle fonction.
1. Vérifier que votre programme génère désormais des passphrases en français.
```

```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
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 - Écrire dans un fichier

**Objectif :** Écrire un mot de passe généré dans un fichier au lieu de l'afficher en console

```admonish travail
1. Dans votre module principal (`main.py`), créer une fonction `write_password_in_file` qui prendra en paramètres : 
    - `password`
    - `filename` avec une valeur par défaut `passwords.txt`
2. Dans cette fonction, ouvrir votre fichier en mode "append" avec un gestionnaire de contexte.
3. Écrire le contenu de la variable `password` dans le fichier
4. Créer une nouvelle entrée dans votre menu intitulée `5 - Générer un fichier avec des passphrases`. 
    - Adapter votre fonction `choix_utilisateur` pour gérer ce nouveau cas. 
    - Vous ferez appel aux fonctions `generer_passphrase` et `write_password_in_file` pour écrire votre passphrase dans un fichier.
5. Testez votre programme pour vérifier que vos passphrases s'écrivent bien dans le fichier.
```

```admonish note title="Dans le compte rendu"
1. Donnez le code de votre nouvelle fonction
1. Que constatez-vous dans le fichier si vous appelez plusieurs fois votre programme à la suite ?
2. Quelle est la différence entre le mode "write" et le mode "append" ?
3. Quel mode puis-je utiliser si je veux à la fois lire et écrire dans mon fichier ?
```

### 3 - Faire un vrai jet de dés

```admonish warning title="Attention"
Dans la suite, nous allons aller au bout du processus "diceware" décrit par l'EFF dans le document du TP3. C'était déjà l'objet de l'étape 6 du TP précédent.

Les personnes ayant terminé l'étape 6 dans le TP précédent (celle qui vous faisait tirer les dés et manipuler des dictionnaires) :
- Dans votre compte-rendu, aux parties 3 et 4, répondez aux questions et ajoutez votre code déjà réalisé
- Passez ensuite au bonus `Lire le dictionnaire à partir d'un fichier` de ce TP.
- Vous pourrez ensuite réaliser les bonus des TP précédents.
```

**Objectif :**  Tirer 5 dés et les assembler en une chaîne de caractères

```admonish travail
1. Créer une nouvelle fonction `tirer_les_des` dans votre module `mdp`.
2. Dans cette fonction, commencer par créer une variable `dice` qui sera une chaîne de caractères vide.
3. 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`
4. A la fin de la boucle, retourner la valeur de `dice`
5. Vérifiez que votre fonction retourne une chaîne de caractères aléatoire ressemblant à `"12345"`
```

```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.
```

```admonish note title="Dans le compte rendu"
1. Mettez le code de votre fonction `tirer_les_des`
```

### 4 - Générer une passphrase tirée aux dés

**Objectif :** Utiliser votre tirage de dés pour sélectionner les mots dans un dictionnaire Python

```admonish travail
1. Créer une fonction `generer_dice_passphrase` qui sera une copie de `generer_passphrase` pour commencer
1. Remplacer l'appel à `get_word_list_from_file` par `get_word_dict` du module `eff_words`. Cette fonction retourne un dictionnaire dont les **clés** sont les tirages de dés et les **valeurs** sont les mots.
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.
```

```admonish note title="Dans le compte rendu"
1. Donnez le code de votre nouvelle fonction `generer_dice_passphrase`
1. Quelle est la différence entre la syntaxe `mon_dict[key]` et `mon_dict.get(key)`, sachant que `mon_dict` est un dictionnaire python ?
```


## Pour aller plus loin

### Bonus facile - Docstrings au format Numpy

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


### Bonus - Lire les dictionnaires à partir d'un fichier

**Objectif** : Lire le dictionnaire utilisé dans votre fonction `generer_dice_passphrase` depuis le fichier `bonus_wordlist_fr.txt`

```admonish travail
1. Dans votre module `mdp`, 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"`.
1. Ouvrir le fichier à l'aide d'un **gestionnaire de contexte**
1. Lire toutes les lignes du fichier, et les stocker dans la variable `lines`
1. Il va falloir maintenant construire votre dictionnaire à partir des lignes lues. 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)
1. Retournez votre dictionnaire
1. Remplacer l'appel à la fonction `get_word_dict` dans votre fonction `generer_dice_passphrase` par votre nouvelle fonction.
1. Vérifier que votre programme génère désormais des passphrases en français.
```

```admonish help title="Aide"
- 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.
```

### Ressources

- [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/)