Momentálně může kterýkoliv uživatel naší aplikace vytvářet a upravovat detaily o všech večeřích. Měli bychom tuto svobodu trochu omezit, a proto dnes zavedeme podporu pro registrace a přihlašování.
Pro začátek si uděláme trochu pořádek v terminologii a vysvětlíme si, co to je autentizace a autorizace.
Autentizace
Autentizace si klade za cíl zjistit, kdo uživatel je. ASP.NET podporuje hned několik typů autentizace, například Windows nebo Passport autentizaci, ale nejčastěji používaná je Forms autentizace. Ta nám umožňuje vytvořit si vlastní formulář pro přihlašování (zatímco u Windows autentizace se objeví systémový přihlašovací dialog a u Passport je uživatel přesměrován na jiný web) a zadané údaje pak jsou porovnány s databází (nebo jiným úložištěm). Pokud je kombinace jména a hesla správná, můžeme vytvořit cookie, která bude jakýmsi „průkazem totožnosti“ uživatele během užívání aplikace. Forms autentizaci použijeme i my.
Autorizace
Jakmile je uživatel autentizován, můžeme přejít k autorizaci. Ta zjišťuje, jaká má přihlášený uživatel práva přístupu k různým částem aplikace nebo k provedení nějaké akce. My například budeme chtít, aby jen přihlášení uživatelé mohli otevřít URL „/Dinners/Create“ a aby večeře mohl upravit jen ten, kdo je vytvořil.
AccountController
Výchozí aplikace, kterou ASP.NET MVC vytvoří do nového projektu, už v základu umožňuje formulářovou autentizaci a registrace, včetně všech potřebných formulářů. Velmi to usnadňuje práci, my vlastně nemusíme dělat vůbec nic. Já jen počeštil jednotlivé views, které najdete v adresáři „/Views/Account/“.
Zkusíme se zaregistrovat, takže aplikaci spustíme a klikneme na tlačítko „Přihlásit“ vpravo nahoře. Svůj účet ještě nemáme, proto přejdeme na stránku s registrací pomocí odkazu na stránce.
Stačilo rovnou zadat URL „/Account/Register“, ale proč se na chvíli nevžít do role uživatele? Na této stránce už se můžeme zaregistrovat:
O celé přihlašování, odhlašování a registrace se stará AccountController, který byl vytvořen společně s MVC projektem. Tento controller využívá známé Membership API, pomocí kterého spravuje všechny zaregistrované uživatele uložené v databázi. Pokud v Solution Exploreru klikneme na tlačítko „Show All Files“, pak tento databázový soubor uvidíme v adresáři App_Data:
Omezení přístupu k /Dinners/Create
Registrace už máme (vlastně jsme je měli už od začátku), ale vytvářet večeře pořád může i anonymní uživatel. Zamezení přístupu nezaregistrovaným uživatelům k metodě Create je dost snadné, stačí jen přidat atribut Authorize:
// GET: /Dinners/Create
[Authorize]
public ActionResult Create()
{
// implementace
}
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner)
{
// implementace
}
ASP.NET MVC totiž umožňuje tvorbu tzv. „akčních filtrů“ (action filters), které pak můžeme deklarativně aplikovat na akční metody, případně i třídy. Authorize je jedním z nich, díky němu můžeme nařídit, aby uživatel musel být přihlášený, pokud chce označenou metodu zavolat. Když nebude přihlášený, bude automaticky přesměrován na stránku s přihlašovacím formulářem. Pokud k tomuto dojde, předá se původní URL jako querystring parametr (například /Account/LogOn?ReturnUrl=%2fDinners%2fCreate) a jakmile se uživatel přihlásí, bude přesměrován zpět na stránku, kterou se pokoušel otevřít.
Pokud chceme, můžeme specifikovat i jednotlivé povolené uživatele, případně uživatelské role. Požadavek pak bude takový, aby uživatel byl zároveň přihlášený a zároveň přítomen mezi specifikovanými uživateli. Takhle vypadá kód, který dovolí přístup jen uživateli se jménem Chrasty:
// GET: /Dinners/Create
[Authorize(Users = "Chrasty")]
public ActionResult Create()
{
// ...
}
Je asi jasné, že ručně vypisovat jednotlivé uživatele je trochu nepraktické, proto je lepším způsobem rozčlenit uživatele do rolí (například administrátoři, moderátoři, uživatelé, …) a nastavit atribut Authorize následovně:
// GET: /Dinners/Create
[Authorize(Roles = "admin")]
public ActionResult Create()
{
// ...
}
My se obejdeme bez jakýchkoli úprav a jen nám bude stačit, aby byl uživatel přihlášený.
Vlastnost User.Identity.Name při vytváření večeří
Jméno přihlášeného uživatele můžeme získat kdekoliv v aplikaci pomocí vlastnosti User.Identity.Name, takže toho hned využijeme v POST verzi metody Create, kde jsme doteď měli natvrdo nastaveného zakladatele večeře na „Někdo“. Když už jsme u toho, tak rovnou ke každé nově vytvořené večeři automaticky přidáme i jeden RSVP objekt reprezentující prvního účastníka – hostitele večeře.
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
try
{
dinner.HostedBy = User.Identity.Name;
RSVP rsvp = new RSVP();
rsvp.AttendeeName = User.Identity.Name;
dinner.RSVPs.Add(rsvp);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(new DinnerFormViewModel(dinner));
}
Nemusíme vůbec kontrolovat, jestli náhodou User.Identity.Name není null, protože aby se metoda Create vůbec zavolala, musí být uživatel přihlášený, tudíž tato vlastnost bude vždy obsahovat nějaké jméno.
Vlastnost User.Identity.Name při upravování večeří
Teď ještě nastavíme, aby uživatel mohl upravit večeři, jen pokud ji sám vytvořil.
Vytvoříme si pomocnou metodu IsHostedBy uvnitř třídy Dinner. Tato metoda bude vracet true nebo false v závislosti na tom, zda se předané jméno v parametru rovná hodnotě vlastnosti HostedBy (mimo to během porovnávání ignoruje velikost písmen):
public bool IsHostedBy(string userName)
{
return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
}
Stejně jako při vytváření večeří, i tady přidáme atribut Authorize k akčním metodám Edit. Pak zavoláme naši novou metodu IsHostedBy, abychom porovnali, zda se přihlášené jméno rovná hostiteli upravované večeře. Pokud to tak není, zobrazíme chybový view „InvalidOwner“ (viz níže):
// GET: /Dinners/Edit/2
[Authorize]
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection formValues)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
try
{
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id = dinner.DinnerID});
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
Musíme přidat šablonu InvalidOwner, jak se dělá, už určitě víte. Není nutné cokoliv nastavovat, jen ji pojmenujte „InvalidOwner“ a vložte do ní tento kód:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Tato večeře není Vaše
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Chyba při přístupu k večeři</h2>
<p>Omlouvám se, ale jen hostitel večeře ji může upravit nebo smazat.</p>
</asp:Content>
Stejný postup použijeme i pro metody Delete – opět jim dáme atribut Authorize a vložíme do nich na vhodné místo kontrolu uživatelského jména a zobrazení naší nové view šablony.
Zobrazení/skrytí odkazů pro úpravu a mazání
Momentálně zobrazujeme odkazy pro smazání a upravování večeří každému uživateli bez výjimky. To teď upravíme, aby je viděl jen hostitel večeře. Bude to skutečně snadné, stačí jen vlézt do šablony Details a před zobrazením odkazů přidat jednoduchou podmínku:
<% if (Model.IsHostedBy(Context.User.Identity.Name)) { %>
<%= Html.ActionLink("Upravit večeři", "Edit", new { id = Model.DinnerID })%>
<%= Html.ActionLink("Smazat večeři", "Delete", new { id = Model.DinnerID })%>
<% } %>
To je zase jednou vše, příště uvidíme, jak umožnit zalogovaným uživatelům přihlášení k různým večeřím s použitím AJAXu.