namespace MvcRelyingParty.Controllers {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.Messages;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using MvcRelyingParty.Models;
using RelyingPartyLogic;
[HandleError]
public class AccountController : Controller {
///
/// Initializes a new instance of the class.
///
///
/// This constructor is used by the MVC framework to instantiate the controller using
/// the default forms authentication and OpenID services.
///
public AccountController()
: this(null, null) {
}
///
/// Initializes a new instance of the class.
///
/// The forms auth.
/// The relying party.
///
/// This constructor is not used by the MVC framework but is instead provided for ease
/// of unit testing this type.
///
public AccountController(IFormsAuthentication formsAuth, IOpenIdRelyingParty relyingParty) {
this.FormsAuth = formsAuth ?? new FormsAuthenticationService();
this.RelyingParty = relyingParty ?? new OpenIdRelyingPartyService();
}
///
/// Gets the forms authentication module to use.
///
public IFormsAuthentication FormsAuth { get; private set; }
///
/// Gets the OpenID relying party to use for logging users in.
///
public IOpenIdRelyingParty RelyingParty { get; private set; }
///
/// Prepares a web page to help the user supply his login information.
///
/// The action result.
public ActionResult LogOn() {
return View();
}
///
/// Accepts the login information provided by the user and redirects
/// the user to their Provider to complete authentication.
///
/// The user-supplied identifier.
/// Whether the user wants a persistent cookie.
/// The URL to direct the user to after successfully authenticating.
/// The action result.
[AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult LogOn(string openid_identifier, bool rememberMe, string returnUrl) {
Identifier userSuppliedIdentifier;
if (Identifier.TryParse(openid_identifier, out userSuppliedIdentifier)) {
try {
var request = this.RelyingParty.CreateRequest(openid_identifier, Realm.AutoDetect, Url.ActionFull("LogOnReturnTo"));
request.SetUntrustedCallbackArgument("rememberMe", rememberMe ? "1" : "0");
// This might be signed so the OP can't send the user to a dangerous URL.
// Of course, if that itself was a danger then the site is vulnerable to XSRF attacks anyway.
if (!string.IsNullOrEmpty(returnUrl)) {
request.SetUntrustedCallbackArgument("returnUrl", returnUrl);
}
// Ask for the user's email, not because we necessarily need it to do our work,
// but so we can display something meaningful to the user as their "username"
// when they log in with a PPID from Google, for example.
request.AddExtension(new ClaimsRequest {
Email = DemandLevel.Require,
FullName = DemandLevel.Request,
PolicyUrl = Url.ActionFull("PrivacyPolicy", "Home"),
});
return request.RedirectingResponse.AsActionResult();
} catch (ProtocolException ex) {
ModelState.AddModelError("OpenID", ex.Message);
}
} else {
ModelState.AddModelError("openid_identifier", "This doesn't look like a valid OpenID.");
}
return View();
}
///
/// Handles the positive assertion that comes from Providers to Javascript running in the browser.
///
/// The action result.
///
/// This method instructs ASP.NET MVC to not validate input
/// because some OpenID positive assertions messages otherwise look like
/// hack attempts and result in errors when validation is turned on.
///
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post), ValidateInput(false)]
public ActionResult LogOnReturnToAjax() {
return RelyingPartyUtilities.AjaxReturnTo(this.Request);
}
///
/// Handles the positive assertion that comes from Providers.
///
/// The action result.
///
/// This method instructs ASP.NET MVC to not validate input
/// because some OpenID positive assertions messages otherwise look like
/// hack attempts and result in errors when validation is turned on.
///
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post), ValidateInput(false)]
public ActionResult LogOnReturnTo() {
var response = this.RelyingParty.GetResponse();
if (response != null) {
switch (response.Status) {
case AuthenticationStatus.Authenticated:
var token = RelyingPartyLogic.User.ProcessUserLogin(response);
bool rememberMe = response.GetUntrustedCallbackArgument("rememberMe") == "1";
this.FormsAuth.SignIn(token.ClaimedIdentifier, rememberMe);
string returnUrl = response.GetUntrustedCallbackArgument("returnUrl");
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl);
} else {
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ModelState.AddModelError("OpenID", "It looks like you canceled login at your OpenID Provider.");
break;
case AuthenticationStatus.Failed:
ModelState.AddModelError("OpenID", response.Exception.Message);
break;
}
}
// If we're to this point, login didn't complete successfully.
// Show the LogOn view again to show the user any errors and
// give another chance to complete login.
return View("LogOn");
}
///
/// Logs the user out of the site and redirects the browser to our home page.
///
/// The action result.
public ActionResult LogOff() {
this.FormsAuth.SignOut();
return RedirectToAction("Index", "Home");
}
public JsonResult Discover(string identifier) {
if (!this.Request.IsAjaxRequest()) {
throw new InvalidOperationException();
}
return RelyingPartyUtilities.AjaxDiscover(identifier, Realm.AutoDetect, Url.ActionFull("LogOnReturnToAjax"));
}
[Authorize]
public ActionResult Edit() {
return View(GetAccountInfoModel());
}
///
/// Updates the user's account information.
///
/// The first name.
/// The last name.
/// The email address.
/// An updated view showing the new profile.
///
/// This action accepts PUT because this operation is idempotent in nature.
///
[Authorize, AcceptVerbs(HttpVerbs.Put), ValidateAntiForgeryToken]
public ActionResult Update(string firstName, string lastName, string emailAddress) {
Database.LoggedInUser.FirstName = firstName;
Database.LoggedInUser.LastName = lastName;
if (Database.LoggedInUser.EmailAddress != emailAddress) {
Database.LoggedInUser.EmailAddress = emailAddress;
Database.LoggedInUser.EmailAddressVerified = false;
}
return PartialView("EditFields", GetAccountInfoModel());
}
[Authorize]
public ActionResult Authorize() {
if (OAuthServiceProvider.PendingAuthorizationRequest == null) {
return RedirectToAction("Edit");
}
var model = new AccountAuthorizeModel {
ConsumerApp = OAuthServiceProvider.PendingAuthorizationConsumer.Name,
IsUnsafeRequest = OAuthServiceProvider.PendingAuthorizationRequest.IsUnsafeRequest,
};
return View(model);
}
[Authorize, AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Authorize(bool isApproved) {
if (isApproved) {
var consumer = OAuthServiceProvider.PendingAuthorizationConsumer;
var tokenManager = OAuthServiceProvider.ServiceProvider.TokenManager;
var pendingRequest = OAuthServiceProvider.PendingAuthorizationRequest;
ITokenContainingMessage requestTokenMessage = pendingRequest;
var requestToken = tokenManager.GetRequestToken(requestTokenMessage.Token);
var response = OAuthServiceProvider.AuthorizePendingRequestTokenAsWebResponse();
if (response != null) {
// The consumer provided a callback URL that can take care of everything else.
return response.AsActionResult();
}
var model = new AccountAuthorizeModel {
ConsumerApp = consumer.Name,
};
if (!pendingRequest.IsUnsafeRequest) {
model.VerificationCode = ServiceProvider.CreateVerificationCode(consumer.VerificationCodeFormat, consumer.VerificationCodeLength);
requestToken.VerificationCode = model.VerificationCode;
tokenManager.UpdateToken(requestToken);
}
return View("AuthorizeApproved", model);
} else {
OAuthServiceProvider.PendingAuthorizationRequest = null;
return View("AuthorizeDenied");
}
}
[Authorize, AcceptVerbs(HttpVerbs.Delete)] // ValidateAntiForgeryToken would be GREAT here, but it's not a FORM POST operation so that doesn't work.
public ActionResult RevokeToken(string token) {
if (String.IsNullOrEmpty(token)) {
throw new ArgumentNullException("token");
}
var tokenEntity = Database.DataContext.IssuedTokens.OfType().Where(t => t.User.UserId == Database.LoggedInUser.UserId && t.Token == token).FirstOrDefault();
if (tokenEntity == null) {
throw new ArgumentOutOfRangeException("id", "The logged in user does not have a token with this name to revoke.");
}
Database.DataContext.DeleteObject(tokenEntity);
Database.DataContext.SaveChanges(); // make changes now so the model we fill up reflects the change
return PartialView("AuthorizedApps", GetAccountInfoModel());
}
[Authorize, AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult AddAuthenticationTokenReturnTo(string openid_identifier) {
var response = this.RelyingParty.GetResponse();
if (response != null) {
switch (response.Status) {
case AuthenticationStatus.Authenticated:
Database.LoggedInUser.AuthenticationTokens.Add(new AuthenticationToken {
ClaimedIdentifier = response.ClaimedIdentifier,
FriendlyIdentifier = response.FriendlyIdentifierForDisplay,
});
Database.DataContext.SaveChanges();
break;
default:
break;
}
}
return RedirectToAction("Edit");
}
[Authorize, AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult AddAuthenticationToken(string openid_identifier) {
Identifier userSuppliedIdentifier;
if (Identifier.TryParse(openid_identifier, out userSuppliedIdentifier)) {
try {
var request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, Realm.AutoDetect, Url.ActionFull("AddAuthenticationTokenReturnTo"));
return request.RedirectingResponse.AsActionResult();
} catch (ProtocolException ex) {
ModelState.AddModelError("openid_identifier", ex);
}
} else {
ModelState.AddModelError("openid_identifier", "This doesn't look like a valid OpenID.");
}
return View("Edit", GetAccountInfoModel());
}
private static AccountInfoModel GetAccountInfoModel() {
var authorizedApps = from token in Database.DataContext.IssuedTokens.OfType()
where token.User.UserId == Database.LoggedInUser.UserId
select new AccountInfoModel.AuthorizedApp { AppName = token.Consumer.Name, Token = token.Token };
Database.LoggedInUser.AuthenticationTokens.Load();
var model = new AccountInfoModel {
FirstName = Database.LoggedInUser.FirstName,
LastName = Database.LoggedInUser.LastName,
EmailAddress = Database.LoggedInUser.EmailAddress,
AuthorizedApps = authorizedApps.ToList(),
AuthenticationTokens = Database.LoggedInUser.AuthenticationTokens.ToList(),
};
return model;
}
}
}