* add Option to specify Production vs. Sandbox Environment also fixes issue https://github.com/TerribleDev/OwinOAuthProviders/issues/54 * ability to specify Production vs. Sandbox environment per auth session in addition to global setting * add examples to show usage of new Production vs. Sandbox Option for Salesforce provider
434 lines
15 KiB
C#
434 lines
15 KiB
C#
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using System.Web.Mvc;
|
|
using Microsoft.AspNet.Identity;
|
|
using Microsoft.AspNet.Identity.EntityFramework;
|
|
using Microsoft.Owin.Security;
|
|
using OwinOAuthProvidersDemo.Models;
|
|
|
|
namespace OwinOAuthProvidersDemo.Controllers
|
|
{
|
|
[Authorize]
|
|
public class AccountController : Controller
|
|
{
|
|
public AccountController()
|
|
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
|
|
{
|
|
}
|
|
|
|
public AccountController(UserManager<ApplicationUser> userManager)
|
|
{
|
|
UserManager = userManager;
|
|
//to support email address as user name
|
|
UserManager.UserValidator = new UserValidator<ApplicationUser>(UserManager) { AllowOnlyAlphanumericUserNames = false };
|
|
}
|
|
|
|
public UserManager<ApplicationUser> UserManager { get; private set; }
|
|
|
|
//
|
|
// GET: /Account/Login
|
|
[AllowAnonymous]
|
|
public ActionResult Login(string returnUrl)
|
|
{
|
|
ViewBag.ReturnUrl = returnUrl;
|
|
return View();
|
|
}
|
|
|
|
//
|
|
// POST: /Account/Login
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
|
|
{
|
|
if (ModelState.IsValid)
|
|
{
|
|
var user = await UserManager.FindAsync(model.UserName, model.Password);
|
|
if (user != null)
|
|
{
|
|
await SignInAsync(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 ActionResult Register()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
//
|
|
// POST: /Account/Register
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<ActionResult> Register(RegisterViewModel model)
|
|
{
|
|
if (ModelState.IsValid)
|
|
{
|
|
var user = new ApplicationUser() { UserName = model.UserName };
|
|
var result = await UserManager.CreateAsync(user, model.Password);
|
|
if (result.Succeeded)
|
|
{
|
|
await SignInAsync(user, isPersistent: false);
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
else
|
|
{
|
|
AddErrors(result);
|
|
}
|
|
}
|
|
|
|
// If we got this far, something failed, redisplay form
|
|
return View(model);
|
|
}
|
|
|
|
//
|
|
// POST: /Account/Disassociate
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<ActionResult> Disassociate(string loginProvider, string providerKey)
|
|
{
|
|
ManageMessageId? message = null;
|
|
IdentityResult result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey));
|
|
if (result.Succeeded)
|
|
{
|
|
message = ManageMessageId.RemoveLoginSuccess;
|
|
}
|
|
else
|
|
{
|
|
message = ManageMessageId.Error;
|
|
}
|
|
return RedirectToAction("Manage", new { Message = message });
|
|
}
|
|
|
|
//
|
|
// GET: /Account/Manage
|
|
public ActionResult 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();
|
|
ViewBag.ReturnUrl = Url.Action("Manage");
|
|
return View();
|
|
}
|
|
|
|
//
|
|
// POST: /Account/Manage
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<ActionResult> Manage(ManageUserViewModel model)
|
|
{
|
|
var hasPassword = HasPassword();
|
|
ViewBag.HasLocalPassword = hasPassword;
|
|
ViewBag.ReturnUrl = Url.Action("Manage");
|
|
if (hasPassword)
|
|
{
|
|
if (ModelState.IsValid)
|
|
{
|
|
IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
|
|
if (result.Succeeded)
|
|
{
|
|
return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
|
|
}
|
|
else
|
|
{
|
|
AddErrors(result);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// User does not have a password so remove any validation errors caused by a missing OldPassword field
|
|
ModelState state = ModelState["OldPassword"];
|
|
if (state != null)
|
|
{
|
|
state.Errors.Clear();
|
|
}
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
|
|
if (result.Succeeded)
|
|
{
|
|
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
|
|
////[HttpPost]
|
|
////[AllowAnonymous]
|
|
////[ValidateAntiForgeryToken]
|
|
////public ActionResult ExternalLogin(string provider, string returnUrl)
|
|
////{
|
|
//// // Request a redirect to the external login provider
|
|
//// return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
|
|
////}
|
|
|
|
//
|
|
// POST: /Account/ExternalLogin
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public ActionResult ExternalLogin(string provider, string returnUrl, string shopName = "")
|
|
{
|
|
// Request a redirect to the external login provider
|
|
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }), null, shopName);
|
|
}
|
|
|
|
//
|
|
// GET: /Account/ExternalLoginCallback
|
|
[AllowAnonymous]
|
|
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
|
|
{
|
|
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
|
|
if (loginInfo == null)
|
|
{
|
|
return RedirectToAction("Login");
|
|
}
|
|
|
|
// Sign in the user with this external login provider if the user already has a login
|
|
var user = await UserManager.FindAsync(loginInfo.Login);
|
|
if (user != null)
|
|
{
|
|
await SignInAsync(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
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public ActionResult LinkLogin(string provider, string shopName)
|
|
{
|
|
// Request a redirect to the external login provider to link a login for the current user
|
|
return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId(), shopName);
|
|
}
|
|
|
|
//
|
|
// GET: /Account/LinkLoginCallback
|
|
public async Task<ActionResult> LinkLoginCallback()
|
|
{
|
|
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
|
|
if (loginInfo == null)
|
|
{
|
|
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
|
|
}
|
|
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
|
|
if (result.Succeeded)
|
|
{
|
|
return RedirectToAction("Manage");
|
|
}
|
|
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
|
|
}
|
|
|
|
//
|
|
// POST: /Account/ExternalLoginConfirmation
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
|
|
{
|
|
if (User.Identity.IsAuthenticated)
|
|
{
|
|
return RedirectToAction("Manage");
|
|
}
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
// Get the information about the user from the external login provider
|
|
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
|
|
if (info == null)
|
|
{
|
|
return View("ExternalLoginFailure");
|
|
}
|
|
var user = new ApplicationUser() { UserName = model.UserName };
|
|
var result = await UserManager.CreateAsync(user);
|
|
if (result.Succeeded)
|
|
{
|
|
result = await UserManager.AddLoginAsync(user.Id, info.Login);
|
|
if (result.Succeeded)
|
|
{
|
|
await SignInAsync(user, isPersistent: false);
|
|
return RedirectToLocal(returnUrl);
|
|
}
|
|
}
|
|
AddErrors(result);
|
|
}
|
|
|
|
ViewBag.ReturnUrl = returnUrl;
|
|
return View(model);
|
|
}
|
|
|
|
//
|
|
// POST: /Account/LogOff
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public ActionResult LogOff()
|
|
{
|
|
AuthenticationManager.SignOut();
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
|
|
//
|
|
// GET: /Account/ExternalLoginFailure
|
|
[AllowAnonymous]
|
|
public ActionResult ExternalLoginFailure()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
[ChildActionOnly]
|
|
public ActionResult RemoveAccountList()
|
|
{
|
|
var linkedAccounts = UserManager.GetLogins(User.Identity.GetUserId());
|
|
ViewBag.ShowRemoveButton = HasPassword() || linkedAccounts.Count > 1;
|
|
return (ActionResult)PartialView("_RemoveAccountPartial", linkedAccounts);
|
|
}
|
|
|
|
protected override 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";
|
|
// Used for Shopify external login to provide shopname while building endpoints.
|
|
private const string ShopNameKey = "ShopName";
|
|
|
|
private IAuthenticationManager AuthenticationManager
|
|
{
|
|
get
|
|
{
|
|
return HttpContext.GetOwinContext().Authentication;
|
|
}
|
|
}
|
|
|
|
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
|
|
{
|
|
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
|
|
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
|
|
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
|
|
}
|
|
|
|
private void AddErrors(IdentityResult result)
|
|
{
|
|
foreach (var error in result.Errors)
|
|
{
|
|
ModelState.AddModelError("", error);
|
|
}
|
|
}
|
|
|
|
private bool HasPassword()
|
|
{
|
|
var user = UserManager.FindById(User.Identity.GetUserId());
|
|
if (user != null)
|
|
{
|
|
return user.PasswordHash != null;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public enum ManageMessageId
|
|
{
|
|
ChangePasswordSuccess,
|
|
SetPasswordSuccess,
|
|
RemoveLoginSuccess,
|
|
Error
|
|
}
|
|
|
|
private ActionResult RedirectToLocal(string returnUrl)
|
|
{
|
|
if (Url.IsLocalUrl(returnUrl))
|
|
{
|
|
return Redirect(returnUrl);
|
|
}
|
|
else
|
|
{
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
}
|
|
|
|
private class ChallengeResult : HttpUnauthorizedResult
|
|
{
|
|
public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null, null)
|
|
{
|
|
}
|
|
|
|
public ChallengeResult(string provider, string redirectUri, string userId, string shopName)
|
|
{
|
|
LoginProvider = provider;
|
|
RedirectUri = redirectUri;
|
|
UserId = userId;
|
|
ShopName = shopName;
|
|
}
|
|
|
|
public string LoginProvider { get; set; }
|
|
public string RedirectUri { get; set; }
|
|
public string UserId { get; set; }
|
|
public string ShopName { get; set; }
|
|
|
|
public override void ExecuteResult(ControllerContext context)
|
|
{
|
|
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
|
|
if (UserId != null)
|
|
{
|
|
properties.Dictionary[XsrfKey] = UserId;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(ShopName))
|
|
{
|
|
properties.Dictionary[ShopNameKey] = ShopName;
|
|
}
|
|
|
|
// if use Salesforce as OAuth provider you can ask for Sandbox auth endpoint
|
|
// for this particular request only
|
|
//properties.Dictionary.Add(
|
|
// Owin.Security.Providers.Salesforce.Constants.EnvironmentAuthenticationProperty,
|
|
// Owin.Security.Providers.Salesforce.Constants.SandboxEnvironment
|
|
// );
|
|
|
|
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
} |