using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions.Security; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.InMemory; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ModelBinding; using MusicStore.Models; using System.Security.Principal; using System.Threading.Tasks; using System.Runtime.CompilerServices; namespace MusicStore.Controllers { //[Authorize] public class AccountController : Controller { public AccountController() //Bug: No EF yet - using an in memory store //: this(new UserManager(new UserStore(new ApplicationDbContext()))) : this(new UserManager(new InMemoryUserStore())) { } public AccountController(UserManager userManager) { UserManager = userManager; } public UserManager UserManager { get; private set; } // // GET: /Account/Login [AllowAnonymous] public IActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); } // // POST: /Account/Login //Bug: HTTP verb attribs not available //[HttpPost] [AllowAnonymous] //[ValidateAntiForgeryToken] public async Task Login(LoginViewModel model, string returnUrl) { if (ModelState.IsValid == true) { var user = await UserManager.Find(model.UserName, model.Password); if (user != null) { await SignIn(user, model.RememberMe); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError("", "Invalid username or password."); } } // If we got this far, something failed, redisplay form return View(model); } // // GET: /Account/Register [AllowAnonymous] public IActionResult Register() { return View(); } // // POST: /Account/Register //Bug: Missing verb attributes //[HttpPost] [AllowAnonymous] //[ValidateAntiForgeryToken] public async Task Register(RegisterViewModel model) { if (ModelState.IsValid == true) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.Create(user, model.Password); if (result.Succeeded) { await SignIn(user, isPersistent: false); //Bug: No helper methods //return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); } // // POST: /Account/Disassociate //Bug: HTTP verbs //[HttpPost] //[ValidateAntiForgeryToken] public async Task Disassociate(string loginProvider, string providerKey) { ManageMessageId? message = null; IdentityResult result = await UserManager.RemoveLogin(this.Context.User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey)); if (result.Succeeded) { message = ManageMessageId.RemoveLoginSuccess; } else { message = ManageMessageId.Error; } //Bug: No helpers available //return RedirectToAction("Manage", new { Message = message }); return View(); } // // GET: /Account/Manage public IActionResult Manage(ManageMessageId? message) { ViewBag.StatusMessage = message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." : message == ManageMessageId.Error ? "An error has occurred." : ""; ViewBag.HasLocalPassword = HasPassword(); //Bug: No Action method with single parameter //ViewBag.ReturnUrl = Url.Action("Manage"); //ViewBag.ReturnUrl = Url.Action("Manage", "Account", null); return View(); } // // POST: /Account/Manage //Bug: No verb attributes //[HttpPost] //[ValidateAntiForgeryToken] public async Task Manage(ManageUserViewModel model) { bool hasPassword = await HasPassword(); ViewBag.HasLocalPassword = hasPassword; //Bug: No Action method with single parameter //ViewBag.ReturnUrl = Url.Action("Manage"); //ViewBag.ReturnUrl = Url.Action("Manage", "Account", null); if (hasPassword) { if (ModelState.IsValid == true) { IdentityResult result = await UserManager.ChangePassword(this.Context.User.Identity.GetUserId(), model.OldPassword, model.NewPassword); if (result.Succeeded) { //Bug: No helper method //return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess }); return View(); } else { AddErrors(result); } } } else { // User does not have a password so remove any validation errors caused by a missing OldPassword field ModelState state = null; ModelState.TryGetValue("OldPassword", out state); if (state != null) { state.Errors.Clear(); } if (ModelState.IsValid == true) { IdentityResult result = await UserManager.AddPassword(this.Context.User.Identity.GetUserId(), model.NewPassword); if (result.Succeeded) { //Bug: No helper method //return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess }); } else { AddErrors(result); } } } // If we got this far, something failed, redisplay form return View(model); } // // POST: /Account/ExternalLogin //Bug: No verb attributes //[HttpPost] [AllowAnonymous] //[ValidateAntiForgeryToken] public IActionResult ExternalLogin(string provider, string returnUrl) { // Request a redirect to the external login provider return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); } // // GET: /Account/ExternalLoginCallback [AllowAnonymous] public async Task ExternalLoginCallback(string returnUrl) { var loginInfo = await this.Context.Response.GetExternalLoginInfo(); if (loginInfo == null) { //Bug: No helper //return RedirectToAction("Login"); return View(); } // Sign in the user with this external login provider if the user already has a login var user = await UserManager.Find(loginInfo.Login); if (user != null) { await SignIn(user, isPersistent: false); return RedirectToLocal(returnUrl); } else { // If the user does not have an account, then prompt the user to create an account ViewBag.ReturnUrl = returnUrl; ViewBag.LoginProvider = loginInfo.Login.LoginProvider; return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName }); } } // // POST: /Account/LinkLogin //Bug: No HTTP verbs //[HttpPost] //[ValidateAntiForgeryToken] public IActionResult LinkLogin(string provider) { // Request a redirect to the external login provider to link a login for the current user return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account", null), this.Context.User.Identity.GetUserId()); } // // GET: /Account/LinkLoginCallback public async Task LinkLoginCallback() { var loginInfo = await this.Context.Response.GetExternalLoginInfo(XsrfKey, this.Context.User.Identity.GetUserId()); if (loginInfo == null) { //Bug: No helper method //return RedirectToAction("Manage", new { Message = ManageMessageId.Error }); return View(); } var result = await UserManager.AddLogin(this.Context.User.Identity.GetUserId(), loginInfo.Login); if (result.Succeeded) { //Bug: No helper method //return RedirectToAction("Manage"); return View(); } //Bug: No helper method //return RedirectToAction("Manage", new { Message = ManageMessageId.Error }); return View(); } // // POST: /Account/ExternalLoginConfirmation //Bug: No HTTP verbs //[HttpPost] [AllowAnonymous] //[ValidateAntiForgeryToken] public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) { if (this.Context.User.Identity.IsAuthenticated) { //Bug: No helper yet //return RedirectToAction("Manage"); return View(); } if (ModelState.IsValid == true) { // Get the information about the user from the external login provider var info = await this.Context.Response.GetExternalLoginInfo(); if (info == null) { return View("ExternalLoginFailure"); } var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.Create(user); if (result.Succeeded) { result = await UserManager.AddLogin(user.Id, info.Login); if (result.Succeeded) { await SignIn(user, isPersistent: false); return RedirectToLocal(returnUrl); } } AddErrors(result); } ViewBag.ReturnUrl = returnUrl; return View(model); } // // POST: /Account/LogOff //Bug: No HTTP verbs //[HttpPost] //[ValidateAntiForgeryToken] public IActionResult LogOff() { this.Context.Response.SignOut(); //Bug: No helper //return RedirectToAction("Index", "Home"); return View(); } // // GET: /Account/ExternalLoginFailure [AllowAnonymous] public IActionResult ExternalLoginFailure() { return View(); } //Bug: Need this attribute //[ChildActionOnly] public async Task RemoveAccountList() { var linkedAccounts = await UserManager.GetLogins(this.Context.User.Identity.GetUserId()); ViewBag.ShowRemoveButton = await HasPassword() || linkedAccounts.Count > 1; //Bug: We dont have partial views yet //return (IActionResult)PartialView("_RemoveAccountPartial", linkedAccounts); return View(); } //Bug: Controllers need to be disposable? protected void Dispose(bool disposing) { if (disposing && UserManager != null) { UserManager.Dispose(); UserManager = null; } //base.Dispose(disposing); } #region Helpers // Used for XSRF protection when adding external logins private const string XsrfKey = "XsrfId"; private async Task SignIn(ApplicationUser user, bool isPersistent) { this.Context.Response.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie); this.Context.Response.SignIn(identity, new AuthenticationProperties() { IsPersistent = isPersistent }); } private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) { ModelState.AddModelError("", error); } } private async Task HasPassword() { var user = await UserManager.FindById(this.Context.User.Identity.GetUserId()); if (user != null) { return user.PasswordHash != null; } return false; } public enum ManageMessageId { ChangePasswordSuccess, SetPasswordSuccess, RemoveLoginSuccess, Error } private IActionResult RedirectToLocal(string returnUrl) { //Bug: No helpers available //if (Url.IsLocalUrl(returnUrl)) //{ // return Redirect(returnUrl); //} //else //{ // return RedirectToAction("Index", "Home"); //} return View(); } private class ChallengeResult : HttpStatusCodeResult { public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null) { } public ChallengeResult(string provider, string redirectUri, string userId) : base(401) { LoginProvider = provider; RedirectUri = redirectUri; UserId = userId; } public string LoginProvider { get; set; } public string RedirectUri { get; set; } public string UserId { get; set; } new public void ExecuteResultAsync(ActionContext context) { var properties = new AuthenticationProperties() { RedirectUri = RedirectUri }; if (UserId != null) { properties.Dictionary[XsrfKey] = UserId; } context.HttpContext.Response.Challenge(LoginProvider, properties); } } #endregion } /// /// TODO: Temporary APIs to unblock build. Need to remove this once we have these APIs available. /// public static class Extensions { public static string GetUserId(this IIdentity user) { return string.Empty; } public static Task GetExternalLoginInfo(this HttpResponse response) { return Task.FromResult(new ExternalLoginInfo()); } public static Task GetExternalLoginInfo(this HttpResponse response, string xsrfKey, string expectedValue) { return Task.FromResult(new ExternalLoginInfo()); } } /// /// TODO: Temporary APIs to unblock build. Need to remove this once we have these APIs available. /// public class ExternalLoginInfo { public string DefaultUserName { get; set; } public UserLoginInfo Login { get; set; } } /// /// TODO: Temporary APIs to unblock build. Need to remove this once we have these APIs available. /// public static class DefaultAuthenticationTypes { public const string ApplicationCookie = "Application"; public const string ExternalCookie = "External"; } }