//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace MvcRelyingParty.Controllers {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.RelyingParty;
using RelyingPartyLogic;
public class AuthController : 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 AuthController()
: 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 AuthController(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; }
private Uri PrivacyPolicyUrl {
get {
return Url.ActionFull("PrivacyPolicy", "Home");
}
}
///
/// Performs discovery on a given identifier.
///
/// The identifier on which to perform discovery.
/// The JSON result of discovery.
public ActionResult Discover(string identifier) {
if (!this.Request.IsAjaxRequest()) {
throw new InvalidOperationException();
}
return this.RelyingParty.AjaxDiscovery(
identifier,
Realm.AutoDetect,
Url.ActionFull("PopUpReturnTo"),
this.PrivacyPolicyUrl);
}
///
/// Prepares a web page to help the user supply his login information.
///
/// The action result.
public ActionResult LogOn() {
this.PreloadDiscoveryResults();
return View();
}
///
/// Prepares a web page to help the user supply his login information.
///
/// The action result.
public ActionResult LogOnPopUp() {
this.PreloadDiscoveryResults();
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 PopUpReturnTo() {
return this.RelyingParty.ProcessAjaxOpenIdResponse();
}
///
/// Handles the positive assertion that comes from Providers.
///
/// The positive assertion obtained via AJAX.
/// 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.Post), ValidateInput(false)]
public ActionResult LogOnPostAssertion(string openid_openidAuthData) {
IAuthenticationResponse response;
if (!string.IsNullOrEmpty(openid_openidAuthData)) {
var auth = new Uri(openid_openidAuthData);
var headers = new WebHeaderCollection();
foreach (string header in Request.Headers) {
headers[header] = Request.Headers[header];
}
// Always say it's a GET since the payload is all in the URL, even the large ones.
HttpRequestInfo clientResponseInfo = new HttpRequestInfo("GET", auth, auth.PathAndQuery, headers, null);
response = this.RelyingParty.GetResponse(clientResponseInfo);
} else {
response = this.RelyingParty.GetResponse();
}
if (response != null) {
switch (response.Status) {
case AuthenticationStatus.Authenticated:
var token = RelyingPartyLogic.User.ProcessUserLogin(response);
this.FormsAuth.SignIn(token.ClaimedIdentifier, false);
string returnUrl = Request.Form["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");
}
[Authorize, AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken, ValidateInput(false)]
public ActionResult AddAuthenticationToken(string openid_openidAuthData) {
IAuthenticationResponse response;
if (!string.IsNullOrEmpty(openid_openidAuthData)) {
var auth = new Uri(openid_openidAuthData);
var headers = new WebHeaderCollection();
foreach (string header in Request.Headers) {
headers[header] = Request.Headers[header];
}
// Always say it's a GET since the payload is all in the URL, even the large ones.
HttpRequestInfo clientResponseInfo = new HttpRequestInfo("GET", auth, auth.PathAndQuery, headers, null);
response = this.RelyingParty.GetResponse(clientResponseInfo);
} else {
response = this.RelyingParty.GetResponse();
}
if (response != null) {
switch (response.Status) {
case AuthenticationStatus.Authenticated:
string identifierString = response.ClaimedIdentifier;
var existing = Database.DataContext.AuthenticationTokens.Include("User").FirstOrDefault(token => token.ClaimedIdentifier == identifierString);
if (existing == null) {
Database.LoggedInUser.AuthenticationTokens.Add(new AuthenticationToken {
ClaimedIdentifier = response.ClaimedIdentifier,
FriendlyIdentifier = response.FriendlyIdentifierForDisplay,
});
Database.DataContext.SaveChanges();
} else {
if (existing.User != Database.LoggedInUser) {
// The supplied token is already bound to a different user account.
// TODO: communicate the problem to the user.
}
}
break;
default:
break;
}
}
return RedirectToAction("Edit", "Account");
}
///
/// 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");
}
///
/// Preloads discovery results for the OP buttons we display on the selector in the ViewData.
///
private void PreloadDiscoveryResults() {
this.ViewData["PreloadedDiscoveryResults"] = this.RelyingParty.PreloadDiscoveryResults(
Realm.AutoDetect,
Url.ActionFull("PopUpReturnTo"),
this.PrivacyPolicyUrl,
"https://me.yahoo.com/",
"https://www.google.com/accounts/o8/id");
}
}
}