//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Mvc {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.UI;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.RelyingParty;
using Validation;
///
/// Methods that generate HTML or Javascript for hosting AJAX OpenID "controls" on
/// ASP.NET MVC web sites.
///
public static class OpenIdHelper {
///
/// Emits a series of stylesheet import tags to support the AJAX OpenID Selector.
///
/// The on the view.
/// HTML that should be sent directly to the browser.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
public static string OpenIdSelectorStyles(this HtmlHelper html) {
Requires.NotNull(html, "html");
using (var result = new StringWriter(CultureInfo.CurrentCulture)) {
result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName);
result.WriteStylesheetLink(OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName);
return result.ToString();
}
}
///
/// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector.
///
/// The on the view.
/// HTML that should be sent directly to the browser.
public static string OpenIdSelectorScripts(this HtmlHelper html) {
return OpenIdSelectorScripts(html, null, null);
}
///
/// Emits a series of script import tags and some inline script to support the AJAX OpenID Selector.
///
/// The on the view.
/// An optional instance of an control, whose properties have been customized to express how this MVC control should be rendered.
/// An optional set of additional script customizations.
///
/// HTML that should be sent directly to the browser.
///
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")]
public static string OpenIdSelectorScripts(this HtmlHelper html, OpenIdSelector selectorOptions, OpenIdAjaxOptions additionalOptions) {
Requires.NotNull(html, "html");
bool selectorOptionsOwned = false;
if (selectorOptions == null) {
selectorOptionsOwned = true;
selectorOptions = new OpenId.RelyingParty.OpenIdSelector();
}
try {
if (additionalOptions == null) {
additionalOptions = new OpenIdAjaxOptions();
}
using (StringWriter result = new StringWriter(CultureInfo.CurrentCulture)) {
if (additionalOptions.ShowDiagnosticIFrame || additionalOptions.ShowDiagnosticTrace) {
string scriptFormat = @"window.openid_visible_iframe = {0}; // causes the hidden iframe to show up
window.openid_trace = {1}; // causes lots of messages";
result.WriteScriptBlock(string.Format(
CultureInfo.InvariantCulture,
scriptFormat,
additionalOptions.ShowDiagnosticIFrame ? "true" : "false",
additionalOptions.ShowDiagnosticTrace ? "true" : "false"));
}
var scriptResources = new[] {
OpenIdRelyingPartyControlBase.EmbeddedJavascriptResource,
OpenIdRelyingPartyAjaxControlBase.EmbeddedAjaxJavascriptResource,
OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName,
};
result.WriteScriptTags(scriptResources);
if (selectorOptions.DownloadYahooUILibrary) {
result.WriteScriptTagsUrls(new[] { "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js" });
}
using (var blockBuilder = new StringWriter(CultureInfo.CurrentCulture)) {
if (selectorOptions.DownloadYahooUILibrary) {
blockBuilder.WriteLine(@" try {
if (YAHOO) {
var loader = new YAHOO.util.YUILoader({
require: ['button', 'menu'],
loadOptional: false,
combine: true
});
loader.insert();
}
} catch (e) { }");
}
blockBuilder.WriteLine("window.aspnetapppath = '{0}';", VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath));
// Positive assertions can last no longer than this library is willing to consider them valid,
// and when they come with OP private associations they last no longer than the OP is willing
// to consider them valid. We assume the OP will hold them valid for at least five minutes.
double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(OpenIdElement.Configuration.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Messaging.MaximumMessageLifetime.TotalMilliseconds));
blockBuilder.WriteLine(
"{0} = {1};",
OpenIdRelyingPartyAjaxControlBase.MaxPositiveAssertionLifetimeJsName,
assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture));
if (additionalOptions.PreloadedDiscoveryResults != null) {
blockBuilder.WriteLine(additionalOptions.PreloadedDiscoveryResults);
}
string discoverUrl = VirtualPathUtility.AppendTrailingSlash(HttpContext.Current.Request.ApplicationPath) + html.RouteCollection["OpenIdDiscover"].GetVirtualPath(html.ViewContext.RequestContext, new RouteValueDictionary(new { identifier = "xxx" })).VirtualPath;
string blockFormat = @" {0} = function (argument, resultFunction, errorCallback) {{
jQuery.ajax({{
async: true,
dataType: 'text',
error: function (request, status, error) {{ errorCallback(status, argument); }},
success: function (result) {{ resultFunction(result, argument); }},
url: '{1}'.replace('xxx', encodeURIComponent(argument))
}});
}};";
blockBuilder.WriteLine(blockFormat, OpenIdRelyingPartyAjaxControlBase.CallbackJSFunctionAsync, discoverUrl);
blockFormat = @" window.postLoginAssertion = function (positiveAssertion) {{
$('#{0}')[0].setAttribute('value', positiveAssertion);
if ($('#{1}')[0] && !$('#{1}')[0].value) {{ // popups have no ReturnUrl predefined, but full page LogOn does.
$('#{1}')[0].setAttribute('value', window.parent.location.href);
}}
document.forms[{2}].submit();
}};";
blockBuilder.WriteLine(
blockFormat,
additionalOptions.AssertionHiddenFieldId,
additionalOptions.ReturnUrlHiddenFieldId,
additionalOptions.FormKey);
blockFormat = @" $(function () {{
var box = document.getElementsByName('openid_identifier')[0];
initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5},
null, // js function to invoke on receiving a positive assertion
{6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17},
false, // auto postback
null); // PostBackEventReference (unused in MVC)
}});";
blockBuilder.WriteLine(
blockFormat,
MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenIdTextBox.EmbeddedLogoResourceName)),
MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName)),
MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)),
MessagingUtilities.GetSafeJavascriptValue(Util.GetWebResourceUrl(typeof(OpenIdRelyingPartyControlBase), OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName)),
selectorOptions.Throttle,
selectorOptions.Timeout.TotalMilliseconds,
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnText),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnToolTip),
selectorOptions.TextBox.ShowLogOnPostBackButton ? "true" : "false",
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnPostBackToolTip),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryText),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.RetryToolTip),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.BusyToolTip),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.IdentifierRequiredMessage),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.LogOnInProgressMessage),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationSucceededToolTip),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticatedAsToolTip),
MessagingUtilities.GetSafeJavascriptValue(selectorOptions.TextBox.AuthenticationFailedToolTip));
result.WriteScriptBlock(blockBuilder.ToString());
result.WriteScriptTags(OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName);
Reporting.RecordFeatureUse("MVC " + typeof(OpenIdSelector).Name);
return result.ToString();
}
}
} catch {
if (selectorOptionsOwned) {
selectorOptions.Dispose();
}
throw;
}
}
///
/// Emits the HTML to render an OpenID Provider button as a part of the overall OpenID Selector UI.
///
/// The on the view.
/// The OP Identifier.
/// The URL of the image to display on the button.
///
/// HTML that should be sent directly to the browser.
///
public static string OpenIdSelectorOPButton(this HtmlHelper html, Identifier providerIdentifier, string imageUrl) {
Requires.NotNull(html, "html");
Requires.NotNull(providerIdentifier, "providerIdentifier");
Requires.NotNullOrEmpty(imageUrl, "imageUrl");
return OpenIdSelectorButton(html, providerIdentifier, "OPButton", imageUrl);
}
///
/// Emits the HTML to render a generic OpenID button as a part of the overall OpenID Selector UI,
/// allowing the user to enter their own OpenID.
///
/// The on the view.
/// The URL of the image to display on the button.
///
/// HTML that should be sent directly to the browser.
///
public static string OpenIdSelectorOpenIdButton(this HtmlHelper html, string imageUrl) {
Requires.NotNull(html, "html");
Requires.NotNullOrEmpty(imageUrl, "imageUrl");
return OpenIdSelectorButton(html, "OpenIDButton", "OpenIDButton", imageUrl);
}
///
/// Emits the HTML to render the entire OpenID Selector UI.
///
/// The on the view.
/// The buttons to include on the selector.
///
/// HTML that should be sent directly to the browser.
///
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")]
public static string OpenIdSelector(this HtmlHelper html, params SelectorButton[] buttons) {
Requires.NotNull(html, "html");
Requires.NotNull(buttons, "buttons");
using (var writer = new StringWriter(CultureInfo.CurrentCulture)) {
using (var h = new HtmlTextWriter(writer)) {
h.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders");
h.RenderBeginTag(HtmlTextWriterTag.Ul);
foreach (SelectorButton button in buttons) {
var op = button as SelectorProviderButton;
if (op != null) {
h.Write(OpenIdSelectorOPButton(html, op.OPIdentifier, op.Image));
continue;
}
var openid = button as SelectorOpenIdButton;
if (openid != null) {
h.Write(OpenIdSelectorOpenIdButton(html, openid.Image));
continue;
}
ErrorUtilities.VerifySupported(false, "The {0} button is not yet supported for MVC.", button.GetType().Name);
}
h.RenderEndTag(); // ul
if (buttons.OfType().Any()) {
h.Write(OpenIdAjaxTextBox(html));
}
}
return writer.ToString();
}
}
///
/// Emits the HTML to render the control as a part of the overall
/// OpenID Selector UI.
///
/// The on the view.
///
/// HTML that should be sent directly to the browser.
///
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "html", Justification = "Breaking change, and it's an extension method so it's useful.")]
public static string OpenIdAjaxTextBox(this HtmlHelper html) {
return @"