Priorité à la sécurité
Chaque fois que vous créez une nouvelle application web ASP.NET, vous avez la possibilité d'ajouter un module d'authentification. Pour notre application Watchlist, nous avons choisi l'authentification par des comptes individuels. L'authentification, c'est tout simplement le processus par lequel votre application vérifie l'identité d'un utilisateur, généralement par le biais d'un nom d'utilisateur et d'un mot de passe. Cette fonctionnalité est déjà opérationnelle, et vous l'avez testée en inscrivant un nouveau compte d'utilisateur.
Il existe un autre mécanisme de sécurité dans ASP.NET appelé autorisation. Vous en apprendrez beaucoup plus à ce sujet dans le cours sur la sécurité .NET, mais je tiens à vous le présenter rapidement ici. Pour simplifier, l'autorisation est la façon dont vous décidez à quelles parties de votre site les utilisateurs peuvent accéder. Par exemple, voulez-vous permettre aux utilisateurs non inscrits de consulter les listes de films de vos abonnés ? Peuvent-ils ajouter des films à votre base de données, en modifier ou en supprimer ? L'autorisation vous permet de contrôler ces différents éléments. Elle est gérée grâce à l'attribut de données Authorize.
Cet attribut n'est pas difficile à implémenter. Je vous recommande de dresser une liste des contrôleurs et des actions de contrôleur de votre application, et d'attribuer un niveau d'accès à chacun d'eux. Commencez par ces quatre catégories d'accès :
Tous les utilisateurs
Utilisateurs inscrits (authentifiés)
Types précis d'utilisateurs inscrits
Utilisateurs précis
Une fois que vous avez identifié le type d'accès requis par les classes et les actions (méthodes) de votre contrôleur, vous pouvez ajouter l'attribut de données approprié à cette classe ou méthode. Vous pouvez sécuriser une classe entière ou des méthodes individuelles, et la syntaxe est simple. Par exemple, pour autoriser uniquement les utilisateurs inscrits à accéder à tout élément de la classe FilmsController, vous devez ajouter l'attribut de données Authorize directement au-dessus de la déclaration de la classe. Pour utiliser cet attribut, vous devez également ajouter une nouvelle déclaration using :
using Microsoft.AspNetCore.Authorization;
[Authorize]
public class FilmsController : Controller
{
...
}
L'utilisation de cet attribut au-dessus de la définition de la classe sécurise l'ensemble de la classe. Toute requête auprès de l'une des méthodes de la classe nécessitera l'authentification de l'utilisateur par son nom d'utilisateur et son mot de passe. Si l'utilisateur n'est pas connecté, il sera automatiquement redirigé vers la page de connexion.
Vous pouvez également sécuriser des méthodes spécifiques tout en laissant les autres méthodes du même contrôleur librement accessibles. Pour ce faire, vous ne devez pas placer l'attribut Authorize au-dessus de la définition de la classe, mais au-dessus de chaque méthode à sécuriser. Seules ces méthodes nécessiteront l'authentification de l'utilisateur.
Nous pourrions aller plus loin sur le sujet, par exemple en expliquant comment sécuriser des sections de votre application en fonction du type d'utilisateur ou même du nom d'utilisateur. Toutefois, ce n'est pas indispensable pour ce cours et ce projet. Gardons cette discussion pour le cours sur la sécurité ASP.NET.
Liez les données
Les actions du contrôleur reçoivent des données provenant de requêtes HTTP. Ces données ne sont pas transmises en des types de données .NET prédéfinis. Écrire du code pour récupérer chaque valeur et la convertir de chaîne de caractères en type .NET approprié serait compliqué. Le modèle dynamique s'en charge pour vous. La liaison dynamique de données (modèle) :
récupère les données envoyées à partir des routes URL, des champs de formulaire et de la requête ;
transmet les données à la méthode du contrôleur par le biais de ses paramètres ;
convertit les données de la chaîne en types de données .NET appropriés.
Par défaut, toutes les propriétés de modèle envoyées à partir d'un formulaire sont liées à une méthode du contrôleur correspondant avec Bind. La syntaxe se présente comme suit :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Titre,Annee")] Film film)
{
if (ModelState.IsValid)
{
_contexte.Add(film);
await _contexte.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(film);
}
Si une propriété est absente de la liste Bind, sa valeur ne sera pas ajoutée aux données de l'objet entrant. Si vous ajoutez des propriétés à vos modèles de données après avoir généré automatiquement vos contrôleurs, assurez-vous de les insérer dans les listes Bind des méthodes de vos contrôleurs.
Ajoutez une nouvelle fonctionnalité à Watchlist
Dans le chapitre précédent, nous avons apporté plusieurs modifications à la page Index des films pour permettre d'ajouter et de supprimer facilement des films de la liste de films de l'utilisateur. Une fois ces opérations terminées au niveau du front-end, vous devez apporter plusieurs changements au contrôleur FilmsController pour que la vue soit effectivement modifiée.
Tout d'abord, vous avez besoin de l'identifiant de l'utilisateur pour créer sa liste de films à partir de la base de données. Cela signifie que vous devez avoir accès au UserManager, que vous pouvez injecter dans le contrôleur par le biais de son constructeur, tout comme vous le faites avec l'objet de contexte de la base de données. Par exemple :
public class FilmsController : Controller
{
private readonly ApplicationDbContext _contexte;
private readonly UserManager<Utilisateur> _gestionnaire;
public FilmsController(ApplicationDbContext contexte,
UserManager<Utilisateur> gestionnaire)
{
_contexte = contexte;
_gestionnaire = gestionnaire;
}
...
}
Récupérez ensuite les données de l'utilisateur actuel avec UserManager.
public class FilmsController : Controller
{
private readonly ApplicationDbContext _contexte;
private readonly UserManager<Utilisateur> _gestionnaire;
public FilmsController(ApplicationDbContext contexte,
UserManager<Utilisateur> gestionnaire)
{
_contexte = contexte;
_gestionnaire = gestionnaire;
}
[HttpGet]
public async Task<string> RecupererIdUtilisateurCourant()
{
Utilisateur utilisateur = await GetCurrentUserAsync();
return utilisateur?.Id;
}
private Task<Utilisateur> GetCurrentUserAsync() =>
_gestionnaire.GetUserAsync(HttpContext.User);
...
}
Maintenant que vous pouvez obtenir l'identifiant de l'utilisateur, vous pouvez déterminer quels films se trouvent dans sa liste de films pour construire le modèle. Pour l'instant, la méthode Index envoie une liste d'objets Film à la page Index. Vous devez changer ce comportement pour qu'elle envoie une liste d'objets ModeleVueFilm.
public class FilmsController : Controller
{
...
public async Task<IActionResult> Index()
{
var idUtilisateur = await RecupererIdUtilisateurCourant();
var modele = await _contexte.Films.Select(x =>
new ModeleVueFilm
{
IdFilm = x.Id,
Titre = x.Titre,
Annee = x.Annee
}).ToListAsync();
foreach(var item in modele)
{
var m = await _contexte.FilmsUtilisateur.FirstOrDefaultAsync(x =>
x.IdUtilisateur == idUtilisateur && x.IdFilm == item.IdFilm);
if(m != null)
{
item.PresentDansListe = true;
item.Note = m.Note;
item.Vu = m.Vu;
}
}
return View(modele);
}
}
Vous pouvez maintenant ajouter la méthode AjouterSupprimer au contrôleur FilmsController. Cette méthode doit renvoyer un objet JsonResult au lieu d'une vue. De cette façon, vous pouvez mettre à jour le DOM de la page Index sans recharger toute la page. Déterminez si le film est ajouté ou retiré de la liste de films, et renvoyez une valeur en conséquence. Le plus simple est de retourner -1 s'il n'y a pas de changement, 0 si le film est supprimé, et 1 si le film est ajouté. Définissez la méthode comme suit :
[HttpGet]
public async Task<JsonResult> AjouterSupprimer()
{
}
Ajoutez maintenant une variable pour stocker la valeur de retour et récupérer l'identifiant de l'utilisateur.
[HttpGet]
public async Task<JsonResult> AjouterSupprimer()
{
int valret = -1;
var idUtilisateur = await RecupererIdUtilisateurCourant();
}
Ensuite, vérifiez la valeur du paramètre valret pour savoir si vous ajoutez ou supprimez un film. Si la valeur est 1, le film est déjà dans la liste de films et doit donc être supprimé. Si la valeur est 0, le film n'est pas dans la liste de films et doit être ajouté.
[HttpGet]
public async Task<JsonResult> AjouterSupprimer()
{
int valret = -1;
var idUtilisateur = await RecupererIdUtilisateurCourant();
if (valret == 1)
{
// s'il existe un enregistrement dans FilmsUtilisateur qui contient à la fois l'identifiant de l'utilisateur
// et celui du film, alors le film existe dans la liste de films et peut
// être supprimé
var film = _contexte.FilmsUtilisateur.FirstOrDefault(x =>
x.IdFilm == id && x.IdUtilisateur == idUtilisateur);
if (film != null)
{
_contexte.FilmsUtilisateur.Remove(film);
valret = 0;
}
}
else
{
// le film n'est pas dans la liste de films, nous devons donc
// créer un nouvel objet FilmUtilisateur et l'ajouter à la base de données.
_contexte.FilmsUtilisateur.Add(
new FilmUtilisateur
{
IdUtilisateur = idUtilisateur,
IdFilm = id,
Vu = false,
Note = 0
}
);
valret = 1;
}
// nous pouvons maintenant enregistrer les changements dans la base de données
await _contexte.SaveChangesAsync();
// et renvoyer notre valeur de retour (-1, 0 ou 1) au script qui a appelé
// cette méthode depuis la page Index
return Json(valret);
}
En résumé
Dans ce chapitre, vous avez appris à renforcer la sécurité de vos contrôleurs avec l'attribut Authorize.
Vous avez également appris comment .NET MVC établit la liaison entre les attributs de données des vues et les actions (méthodes) des contrôleurs.
Enfin, vous avez apporté plusieurs modifications à votre contrôleur FilmsController pour que les changements apportés à la vue index dans le chapitre précédent fonctionnent comme vous l'aviez prévu, complétant ainsi la spécification fonctionnelle de votre application Watchlist.
Il est maintenant temps d'habiller votre interface !