summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--samples/OpenIdRelyingPartyWebForms/login.aspx5
-rw-r--r--samples/OpenIdRelyingPartyWebForms/login.aspx.cs10
-rw-r--r--samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs2
-rw-r--r--src/DotNetOpenAuth/ComponentModel/ConverterBase.cs32
-rw-r--r--src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs69
-rw-r--r--src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs19
-rw-r--r--src/DotNetOpenAuth/ComponentModel/UriConverter.cs2
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj6
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx3
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs151
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs567
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs33
13 files changed, 888 insertions, 20 deletions
diff --git a/samples/OpenIdRelyingPartyWebForms/login.aspx b/samples/OpenIdRelyingPartyWebForms/login.aspx
index 20294e7..42d40e8 100644
--- a/samples/OpenIdRelyingPartyWebForms/login.aspx
+++ b/samples/OpenIdRelyingPartyWebForms/login.aspx
@@ -23,7 +23,8 @@
<asp:Label ID="setupRequiredLabel" runat="server" EnableViewState="False" Text="You must log into your Provider first to use Immediate mode."
Visible="False" />
<p>
- <asp:ImageButton runat="server" ImageUrl="~/images/yahoo.png" ID="yahooLoginButton"
- OnClick="yahooLoginButton_Click" />
+ <rp:OpenIdButton runat="server" ImageUrl="~/images/yahoo.png"
+ ID="yahooLoginButton" Identifier="https://me.yahoo.com/"
+ />
</p>
</asp:Content>
diff --git a/samples/OpenIdRelyingPartyWebForms/login.aspx.cs b/samples/OpenIdRelyingPartyWebForms/login.aspx.cs
index fb961dd..1de942a 100644
--- a/samples/OpenIdRelyingPartyWebForms/login.aspx.cs
+++ b/samples/OpenIdRelyingPartyWebForms/login.aspx.cs
@@ -39,16 +39,6 @@ namespace OpenIdRelyingPartyWebForms {
this.setupRequiredLabel.Visible = true;
}
- protected void yahooLoginButton_Click(object sender, ImageClickEventArgs e) {
- OpenIdRelyingParty openid = new OpenIdRelyingParty();
- var req = openid.CreateRequest("yahoo.com");
- this.prepareRequest(req);
- req.RedirectToProvider();
-
- // We don't listen for the response from the provider explicitly
- // because the OpenIdLogin control is already doing that for us.
- }
-
private void prepareRequest(IAuthenticationRequest request) {
// Collect the PAPE policies requested by the user.
List<string> policies = new List<string>();
diff --git a/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs
index 60de3ff..944f5ff 100644
--- a/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs
+++ b/samples/OpenIdRelyingPartyWebForms/login.aspx.designer.cs
@@ -56,6 +56,6 @@ namespace OpenIdRelyingPartyWebForms {
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
- protected global::System.Web.UI.WebControls.ImageButton yahooLoginButton;
+ protected global::DotNetOpenAuth.OpenId.RelyingParty.OpenIdButton yahooLoginButton;
}
}
diff --git a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
index a7d58c7..37f9c78 100644
--- a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
+++ b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
@@ -12,6 +12,9 @@ namespace DotNetOpenAuth.ComponentModel {
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
+using System.Reflection;
+ using System.Security;
+ using System.Security.Permissions;
/// <summary>
/// A design-time helper to allow Intellisense to aid typing
@@ -141,6 +144,10 @@ namespace DotNetOpenAuth.ComponentModel {
/// The conversion cannot be performed.
/// </exception>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
+ if (destinationType.IsInstanceOfType(value)) {
+ return value;
+ }
+
T typedValue = (T)value;
if (destinationType == typeof(string)) {
return this.ConvertToString(typedValue);
@@ -152,6 +159,20 @@ namespace DotNetOpenAuth.ComponentModel {
}
/// <summary>
+ /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand.
+ /// </summary>
+ /// <param name="memberInfo">The member info.</param>
+ /// <param name="arguments">The arguments.</param>
+ /// <returns>A <see cref="InstanceDescriptor"/>, or <c>null</c> if sufficient permissions are unavailable.</returns>
+ protected static InstanceDescriptor CreateInstanceDescriptor(MemberInfo memberInfo, ICollection arguments) {
+ try {
+ return CreateInstanceDescriptorPrivate(memberInfo, arguments);
+ } catch (SecurityException) {
+ return null;
+ }
+ }
+
+ /// <summary>
/// Gets the standard values to suggest with Intellisense in the designer.
/// </summary>
/// <returns>A collection of the standard values.</returns>
@@ -185,5 +206,16 @@ namespace DotNetOpenAuth.ComponentModel {
/// <returns>The string representation of the object.</returns>
[Pure]
protected abstract string ConvertToString(T value);
+
+ /// <summary>
+ /// Creates an <see cref="InstanceDescriptor"/> instance, protecting against the LinkDemand.
+ /// </summary>
+ /// <param name="memberInfo">The member info.</param>
+ /// <param name="arguments">The arguments.</param>
+ /// <returns>A <see cref="InstanceDescriptor"/>.</returns>
+ [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
+ private static InstanceDescriptor CreateInstanceDescriptorPrivate(MemberInfo memberInfo, ICollection arguments) {
+ return new InstanceDescriptor(memberInfo, arguments);
+ }
}
}
diff --git a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
new file mode 100644
index 0000000..6ba9c4b
--- /dev/null
+++ b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+// <copyright file="IdentifierConverter.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ComponentModel {
+ using System;
+ using System.Collections;
+ using System.ComponentModel.Design.Serialization;
+ using System.Reflection;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A design-time helper to give an OpenID Identifier property an auto-complete functionality
+ /// listing the OP Identifiers in the <see cref="WellKnownProviders"/> class.
+ /// </summary>
+ public class IdentifierConverter : ConverterBase<Identifier> {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IdentifierConverter"/> class.
+ /// </summary>
+ [Obsolete("This class is meant for design-time use within an IDE, and not meant to be used directly by runtime code.")]
+ public IdentifierConverter() {
+ }
+
+ /// <summary>
+ /// Converts a value from its string representation to its strongly-typed object.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns>The strongly-typed object.</returns>
+ protected override Identifier ConvertFrom(string value) {
+ return value;
+ }
+
+ /// <summary>
+ /// Creates the reflection instructions for recreating an instance later.
+ /// </summary>
+ /// <param name="value">The value to recreate later.</param>
+ /// <returns>
+ /// The description of how to recreate an instance.
+ /// </returns>
+ protected override InstanceDescriptor CreateFrom(Identifier value) {
+ if (value == null) {
+ return null;
+ }
+
+ MemberInfo identifierParse = typeof(Identifier).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
+ return CreateInstanceDescriptor(identifierParse, new object[] { value.ToString() });
+ }
+
+ /// <summary>
+ /// Converts the strongly-typed value to a string.
+ /// </summary>
+ /// <param name="value">The value to convert.</param>
+ /// <returns>The string representation of the object.</returns>
+ protected override string ConvertToString(Identifier value) {
+ return value;
+ }
+
+ /// <summary>
+ /// Gets the standard values to suggest with Intellisense in the designer.
+ /// </summary>
+ /// <returns>A collection of the standard values.</returns>
+ protected override ICollection GetStandardValuesForCache() {
+ return SuggestedStringsConverter.GetStandardValuesForCacheShared(typeof(WellKnownProviders));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs
index 3b60bd7..1c8c555 100644
--- a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs
+++ b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs
@@ -30,6 +30,19 @@ namespace DotNetOpenAuth.ComponentModel {
protected abstract Type WellKnownValuesType { get; }
/// <summary>
+ /// Gets the values of public static fields and properties on a given type.
+ /// </summary>
+ /// <param name="type">The type to reflect over.</param>
+ /// <returns>A collection of values.</returns>
+ internal static ICollection GetStandardValuesForCacheShared(Type type) {
+ var fields = from field in type.GetFields(BindingFlags.Static | BindingFlags.Public)
+ select field.GetValue(null);
+ var properties = from prop in type.GetProperties(BindingFlags.Static | BindingFlags.Public)
+ select prop.GetValue(null, null);
+ return (fields.Concat(properties)).ToArray();
+ }
+
+ /// <summary>
/// Converts a value from its string representation to its strongly-typed object.
/// </summary>
/// <param name="value">The value.</param>
@@ -68,11 +81,7 @@ namespace DotNetOpenAuth.ComponentModel {
/// <returns>A collection of the standard values.</returns>
[Pure]
protected override ICollection GetStandardValuesForCache() {
- var fields = from field in this.WellKnownValuesType.GetFields(BindingFlags.Static | BindingFlags.Public)
- select field.GetValue(null);
- var properties = from prop in this.WellKnownValuesType.GetProperties(BindingFlags.Static | BindingFlags.Public)
- select prop.GetValue(null, null);
- return (fields.Concat(properties)).ToArray();
+ return GetStandardValuesForCacheShared(this.WellKnownValuesType);
}
}
}
diff --git a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs b/src/DotNetOpenAuth/ComponentModel/UriConverter.cs
index 4412199..cf8dde3 100644
--- a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs
+++ b/src/DotNetOpenAuth/ComponentModel/UriConverter.cs
@@ -76,7 +76,7 @@ namespace DotNetOpenAuth.ComponentModel {
}
MemberInfo uriCtor = typeof(Uri).GetConstructor(new Type[] { typeof(string) });
- return new InstanceDescriptor(uriCtor, new object[] { value.AbsoluteUri });
+ return CreateInstanceDescriptor(uriCtor, new object[] { value.AbsoluteUri });
}
/// <summary>
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 7a28841..dab793a 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -174,6 +174,7 @@
<Compile Include="ComponentModel\ClaimTypeSuggestions.cs" />
<Compile Include="ComponentModel\ConverterBase.cs" />
<Compile Include="ComponentModel\IssuersSuggestions.cs" />
+ <Compile Include="ComponentModel\IdentifierConverter.cs" />
<Compile Include="ComponentModel\SuggestedStringsConverter.cs" />
<Compile Include="ComponentModel\UriConverter.cs" />
<Compile Include="Configuration\AssociationTypeCollection.cs" />
@@ -431,9 +432,11 @@
<Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" />
<Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" />
+ <Compile Include="OpenId\RelyingParty\OpenIdButton.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdEventArgs.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdLogin.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdMobileTextBox.cs" />
+ <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdTextBox.cs" />
<Compile Include="OpenId\RelyingParty\PopupBehavior.cs" />
<Compile Include="OpenId\RelyingParty\PositiveAnonymousResponse.cs" />
@@ -461,6 +464,7 @@
<Compile Include="OpenId\RelyingParty\ServiceEndpoint.cs" />
<Compile Include="OpenId\OpenIdXrdsHelper.cs" />
<Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" />
+ <Compile Include="OpenId\RelyingParty\WellKnownProviders.cs" />
<Compile Include="OpenId\SecuritySettings.cs" />
<Compile Include="Messaging\UntrustedWebRequestHandler.cs" />
<Compile Include="OpenId\UriIdentifier.cs" />
@@ -558,4 +562,4 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" />
-</Project>
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index f0e8033..76ec9c4 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -416,6 +416,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to No identifier has been set..
+ /// </summary>
+ internal static string NoIdentifierSet {
+ get {
+ return ResourceManager.GetString("NoIdentifierSet", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to No XRDS document containing OpenID relying party endpoint information could be found at {0}..
/// </summary>
internal static string NoRelyingPartyEndpointDiscovered {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index 4f10cf4..fb6fdc7 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -325,4 +325,7 @@ Discovered endpoint info:
<data name="UnexpectedEnumPropertyValue" xml:space="preserve">
<value>The property {0} had unexpected value {1}.</value>
</data>
+ <data name="NoIdentifierSet" xml:space="preserve">
+ <value>No identifier has been set.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs
new file mode 100644
index 0000000..4f930b0
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs
@@ -0,0 +1,151 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdButton.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Drawing.Design;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Web.UI;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An ASP.NET control that renders a button that initiates an
+ /// authentication when clicked.
+ /// </summary>
+ public class OpenIdButton : OpenIdRelyingPartyControlBase {
+ #region Property defaults
+
+ /// <summary>
+ /// The default value for the <see cref="Identifier"/> property.
+ /// </summary>
+ private const string IdentifierDefault = "https://me.yahoo.com/";
+
+ /// <summary>
+ /// The default value for the <see cref="Text"/> property.
+ /// </summary>
+ private const string TextDefault = "Log in with Yahoo!";
+
+ #endregion
+
+ #region View state keys
+
+ /// <summary>
+ /// The key under which the value for the <see cref="Text"/> property will be stored.
+ /// </summary>
+ private const string TextViewStateKey = "Text";
+
+ /// <summary>
+ /// The key under which the value for the <see cref="ImageUrl"/> property will be stored.
+ /// </summary>
+ private const string ImageUrlViewStateKey = "ImageUrl";
+
+ #endregion
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdButton"/> class.
+ /// </summary>
+ public OpenIdButton() {
+ }
+
+ /// <summary>
+ /// Gets or sets the text to display for the link.
+ /// </summary>
+ [Bindable(true), DefaultValue(TextDefault), Category(AppearanceCategory)]
+ [Description("The text to display for the link.")]
+ public string Text {
+ get { return (string)ViewState[TextViewStateKey] ?? TextDefault; }
+ set { ViewState[TextViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the image to display.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
+ [Bindable(true), Category(AppearanceCategory)]
+ [Description("The image to display.")]
+ [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
+ public string ImageUrl {
+ get {
+ return (string)ViewState[ImageUrlViewStateKey];
+ }
+
+ set {
+ UriUtil.ValidateResolvableUrl(Page, DesignMode, value);
+ ViewState[ImageUrlViewStateKey] = value;
+ }
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event.
+ /// </summary>
+ /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
+ protected override void OnPreRender(EventArgs e) {
+ base.OnPreRender(e);
+
+ if (!this.DesignMode) {
+ ErrorUtilities.VerifyOperation(this.Identifier != null, OpenIdStrings.NoIdentifierSet);
+ }
+ }
+
+ /// <summary>
+ /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client.
+ /// </summary>
+ /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
+ protected override void Render(HtmlTextWriter writer) {
+ if (string.IsNullOrEmpty(this.Identifier)) {
+ writer.WriteEncodedText(string.Format(CultureInfo.CurrentCulture, "[{0}]", OpenIdStrings.NoIdentifierSet));
+ } else {
+ IAuthenticationRequest request = null;
+ Uri beginUrl = null;
+ string errorMessage = null;
+ try {
+ if (!this.DesignMode) {
+ request = this.CreateRequest();
+ beginUrl = request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel);
+ }
+ } catch (ProtocolException ex) {
+ Logger.OpenId.Error("OpenIdButton failed to create OpenID request for identifier.", ex);
+ errorMessage = ex.Message;
+ }
+
+ if (request == null && !this.DesignMode) {
+ writer.AddStyleAttribute("text-decoration", "line-through");
+ if (!string.IsNullOrEmpty(errorMessage)) {
+ writer.AddAttribute(HtmlTextWriterAttribute.Title, errorMessage);
+ }
+ writer.RenderBeginTag(HtmlTextWriterTag.Span);
+ } else {
+ if (beginUrl != null) {
+ writer.AddAttribute(HtmlTextWriterAttribute.Href, beginUrl.AbsoluteUri);
+ }
+
+ writer.RenderBeginTag(HtmlTextWriterTag.A);
+ }
+
+ if (!string.IsNullOrEmpty(this.ImageUrl)) {
+ writer.AddAttribute(HtmlTextWriterAttribute.Src, this.ResolveClientUrl(this.ImageUrl));
+ writer.AddAttribute(HtmlTextWriterAttribute.Border, "0");
+ string tooltip = errorMessage ?? this.Text;
+ if (!string.IsNullOrEmpty(tooltip)) {
+ writer.AddAttribute(HtmlTextWriterAttribute.Alt, tooltip);
+ writer.AddAttribute(HtmlTextWriterAttribute.Title, tooltip);
+ }
+ writer.RenderBeginTag(HtmlTextWriterTag.Img);
+ writer.RenderEndTag();
+ } else if (!string.IsNullOrEmpty(this.Text)) {
+ writer.WriteEncodedText(this.Text);
+ }
+
+ writer.RenderEndTag();
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
new file mode 100644
index 0000000..a35d532
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
@@ -0,0 +1,567 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdRelyingPartyControlBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Drawing.Design;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Web.Security;
+ using System.Web.UI;
+ using DotNetOpenAuth.ComponentModel;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions.UI;
+
+ /// <summary>
+ /// A common base class for OpenID Relying Party controls.
+ /// </summary>
+ public abstract class OpenIdRelyingPartyControlBase : Control {
+ #region Property category constants
+
+ /// <summary>
+ /// The "Appearance" category for properties.
+ /// </summary>
+ protected const string AppearanceCategory = "Appearance";
+
+ /// <summary>
+ /// The "Behavior" category for properties.
+ /// </summary>
+ protected const string BehaviorCategory = "Behavior";
+
+ #endregion
+
+ #region Property default values
+
+ /// <summary>
+ /// The default value for the <see cref="Stateless"/> property.
+ /// </summary>
+ private const bool StatelessDefault = false;
+
+ /// <summary>
+ /// Default value of <see cref="UsePersistentCookie"/>.
+ /// </summary>
+ private const bool UsePersistentCookieDefault = false;
+
+ /// <summary>
+ /// The default value for the <see cref="ReturnToUrl"/> property.
+ /// </summary>
+ private const string ReturnToUrlDefault = "";
+
+ /// <summary>
+ /// The default value for the <see cref="RealmUrl"/> property.
+ /// </summary>
+ private const string RealmUrlDefault = "~/";
+
+ /// <summary>
+ /// The default value for the <see cref="Popup"/> property.
+ /// </summary>
+ private const PopupBehavior PopupDefault = PopupBehavior.Never;
+
+ /// <summary>
+ /// The default value for the <see cref="RequireSsl"/> property.
+ /// </summary>
+ private const bool RequireSslDefault = false;
+
+ #endregion
+
+ #region Property view state keys
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="Stateless"/> property.
+ /// </summary>
+ private const string StatelessViewStateKey = "Stateless";
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property.
+ /// </summary>
+ private const string UsePersistentCookieViewStateKey = "UsePersistentCookie";
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="RealmUrl"/> property.
+ /// </summary>
+ private const string RealmUrlViewStateKey = "RealmUrl";
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="ReturnToUrl"/> property.
+ /// </summary>
+ private const string ReturnToUrlViewStateKey = "ReturnToUrl";
+
+ /// <summary>
+ /// The key under which the value for the <see cref="Identifier"/> property will be stored.
+ /// </summary>
+ private const string IdentifierViewStateKey = "Identifier";
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="Popup"/> property.
+ /// </summary>
+ private const string PopupViewStateKey = "Popup";
+
+ /// <summary>
+ /// The viewstate key to use for the <see cref="RequireSsl"/> property.
+ /// </summary>
+ private const string RequireSslViewStateKey = "RequireSsl";
+
+ #endregion
+
+ #region Callback parameter names
+
+ /// <summary>
+ /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property.
+ /// </summary>
+ private const string UsePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie";
+
+ /// <summary>
+ /// The callback parameter to use for recognizing when the callback is in a popup window.
+ /// </summary>
+ private const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup";
+
+ /// <summary>
+ /// The callback parameter to use for recognizing when the callback is in the parent window.
+ /// </summary>
+ private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent";
+
+ #endregion
+
+ /// <summary>
+ /// Backing field for the <see cref="RelyingParty"/> property.
+ /// </summary>
+ private OpenIdRelyingParty relyingParty;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingPartyControlBase"/> class.
+ /// </summary>
+ protected OpenIdRelyingPartyControlBase() {
+ }
+
+ #region Events
+
+ /// <summary>
+ /// Fired after the user clicks the log in button, but before the authentication
+ /// process begins. Offers a chance for the web application to disallow based on
+ /// OpenID URL before redirecting the user to the OpenID Provider.
+ /// </summary>
+ [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider.")]
+ public event EventHandler<OpenIdEventArgs> LoggingIn;
+
+ /// <summary>
+ /// Fired upon completion of a successful login.
+ /// </summary>
+ [Description("Fired upon completion of a successful login.")]
+ public event EventHandler<OpenIdEventArgs> LoggedIn;
+
+ /// <summary>
+ /// Fired when a login attempt fails.
+ /// </summary>
+ [Description("Fired when a login attempt fails.")]
+ public event EventHandler<OpenIdEventArgs> Failed;
+
+ /// <summary>
+ /// Fired when an authentication attempt is canceled at the OpenID Provider.
+ /// </summary>
+ [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")]
+ public event EventHandler<OpenIdEventArgs> Canceled;
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use.
+ /// </summary>
+ /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value>
+ /// <remarks>
+ /// A performance optimization would be to store off the
+ /// instance as a static member in your web site and set it
+ /// to this property in your <see cref="Control.Load">Page.Load</see>
+ /// event since instantiating these instances can be expensive on
+ /// heavily trafficked web pages.
+ /// </remarks>
+ [Browsable(false)]
+ public OpenIdRelyingParty RelyingParty {
+ get {
+ if (this.relyingParty == null) {
+ this.relyingParty = this.CreateRelyingParty();
+ }
+ return this.relyingParty;
+ }
+
+ set {
+ this.relyingParty = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether stateless mode is used.
+ /// </summary>
+ [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)]
+ [Description("Controls whether stateless mode is used.")]
+ public bool Stateless {
+ get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); }
+ set { ViewState[StatelessViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
+ [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")]
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
+ [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)]
+ [Description("The OpenID Realm of the relying party web site.")]
+ [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
+ public string RealmUrl {
+ get {
+ return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault);
+ }
+
+ set {
+ if (Page != null && !DesignMode) {
+ // Validate new value by trying to construct a Realm object based on it.
+ new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure.
+ } else {
+ // We can't fully test it, but it should start with either ~/ or a protocol.
+ if (Regex.IsMatch(value, @"^https?://")) {
+ new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards
+ } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
+ // this is valid too
+ } else {
+ throw new UriFormatException();
+ }
+ }
+ ViewState[RealmUrlViewStateKey] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the OpenID ReturnTo of the relying party web site.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Bindable property must be simple type")]
+ [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
+ [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)]
+ [Description("The OpenID ReturnTo of the relying party web site.")]
+ [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
+ public string ReturnToUrl {
+ get {
+ return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault);
+ }
+
+ set {
+ if (this.Page != null && !this.DesignMode) {
+ // Validate new value by trying to construct a Uri based on it.
+ new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure.
+ } else {
+ // We can't fully test it, but it should start with either ~/ or a protocol.
+ if (Regex.IsMatch(value, @"^https?://")) {
+ new Uri(value); // make sure it's fully-qualified, but ignore wildcards
+ } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
+ // this is valid too
+ } else {
+ throw new UriFormatException();
+ }
+ }
+
+ this.ViewState[ReturnToUrlViewStateKey] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to send a persistent cookie upon successful
+ /// login so the user does not have to log in upon returning to this site.
+ /// </summary>
+ [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)]
+ [Description("Whether to send a persistent cookie upon successful " +
+ "login so the user does not have to log in upon returning to this site.")]
+ public virtual bool UsePersistentCookie {
+ get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); }
+ set { this.ViewState[UsePersistentCookieViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating when to use a popup window to complete the login experience.
+ /// </summary>
+ /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value>
+ [Bindable(true), DefaultValue(PopupDefault), Category(BehaviorCategory)]
+ [Description("When to use a popup window to complete the login experience.")]
+ public PopupBehavior Popup {
+ get { return (PopupBehavior)(ViewState[PopupViewStateKey] ?? PopupDefault); }
+ set { ViewState[PopupViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enforce on high security mode,
+ /// which requires the full authentication pipeline to be protected by SSL.
+ /// </summary>
+ [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)]
+ [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")]
+ public bool RequireSsl {
+ get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); }
+ set { ViewState[RequireSslViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL to your privacy policy page that describes how
+ /// claims will be used and/or shared.
+ /// </summary>
+ [Bindable(true), Category(BehaviorCategory)]
+ [Description("The OpenID Identifier that this button will use to initiate login.")]
+ [TypeConverter(typeof(IdentifierConverter))]
+ public Identifier Identifier {
+ get { return (Identifier)ViewState[IdentifierViewStateKey]; }
+ set { ViewState[IdentifierViewStateKey] = value; }
+ }
+
+ /// <summary>
+ /// Immediately redirects to the OpenID Provider to verify the Identifier
+ /// provided in the text box.
+ /// </summary>
+ public void LogOn() {
+ IAuthenticationRequest request = this.CreateRequest();
+ if (this.IsPopupAppropriate(request)) {
+ this.ScriptPopupWindow(request);
+ } else {
+ request.RedirectToProvider();
+ }
+ }
+
+ /// <summary>
+ /// Constructs the authentication request and returns it.
+ /// </summary>
+ /// <returns>The instantiated authentication request, or <c>null</c> if a failure occurred.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")]
+ protected virtual IAuthenticationRequest CreateRequest() {
+ Contract.Requires(this.Identifier != null, OpenIdStrings.OpenIdTextBoxEmpty);
+ ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Identifier), OpenIdStrings.OpenIdTextBoxEmpty);
+ IAuthenticationRequest request;
+
+ try {
+ // Approximate the returnTo (either based on the customize property or the page URL)
+ // so we can use it to help with Realm resolution.
+ var requestContext = this.RelyingParty.Channel.GetRequestFromContext();
+ Uri returnToApproximation = this.ReturnToUrl != null ? new Uri(requestContext.UrlBeforeRewriting, this.ReturnToUrl) : this.Page.Request.Url;
+
+ // Resolve the trust root, and swap out the scheme and port if necessary to match the
+ // return_to URL, since this match is required by OpenId, and the consumer app
+ // may be using HTTP at some times and HTTPS at others.
+ UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext());
+ realm.Scheme = returnToApproximation.Scheme;
+ realm.Port = returnToApproximation.Port;
+
+ // Initiate openid request
+ // We use TryParse here to avoid throwing an exception which
+ // might slip through our validator control if it is disabled.
+ Realm typedRealm = new Realm(realm);
+ if (string.IsNullOrEmpty(this.ReturnToUrl)) {
+ request = this.RelyingParty.CreateRequest(this.Identifier, typedRealm);
+ } else {
+ // Since the user actually gave us a return_to value,
+ // the "approximation" is exactly what we want.
+ request = this.RelyingParty.CreateRequest(this.Identifier, typedRealm, returnToApproximation);
+ }
+
+ if (this.IsPopupAppropriate(request)) {
+ // Inform the OP that it will appear in a popup window.
+ request.AddExtension(new UIRequest());
+ }
+
+ // Add state that needs to survive across the redirect.
+ if (!this.Stateless) {
+ request.AddCallbackArguments(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture));
+ }
+
+ this.OnLoggingIn(request);
+ } catch (ProtocolException ex) {
+ this.OnFailed(new FailedAuthenticationResponse(ex));
+ return null;
+ }
+
+ return request;
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:Load"/> event.
+ /// </summary>
+ /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
+ protected override void OnLoad(EventArgs e) {
+ base.OnLoad(e);
+
+ if (Page.IsPostBack) {
+ // OpenID responses NEVER come in the form of a postback.
+ return;
+ }
+
+ var response = this.RelyingParty.GetResponse();
+ if (response != null) {
+ switch (response.Status) {
+ case AuthenticationStatus.Authenticated:
+ this.OnLoggedIn(response);
+ break;
+ case AuthenticationStatus.Canceled:
+ this.OnCanceled(response);
+ break;
+ case AuthenticationStatus.Failed:
+ this.OnFailed(response);
+ break;
+ case AuthenticationStatus.SetupRequired:
+ case AuthenticationStatus.ExtensionsOnly:
+ default:
+ // The NotApplicable (extension-only assertion) is NOT one that we support
+ // in this control because that scenario is primarily interesting to RPs
+ // that are asking a specific OP, and it is not user-initiated as this textbox
+ // is designed for.
+ throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Fires the <see cref="LoggedIn"/> event.
+ /// </summary>
+ /// <param name="response">The response.</param>
+ protected virtual void OnLoggedIn(IAuthenticationResponse response) {
+ Contract.Requires(response != null);
+ Contract.Requires(response.Status == AuthenticationStatus.Authenticated);
+ ErrorUtilities.VerifyArgumentNotNull(response, "response");
+ ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response.");
+
+ var loggedIn = this.LoggedIn;
+ OpenIdEventArgs args = new OpenIdEventArgs(response);
+ if (loggedIn != null) {
+ loggedIn(this, args);
+ }
+
+ if (!args.Cancel) {
+ FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie);
+ }
+ }
+
+ /// <summary>
+ /// Fires the <see cref="LoggingIn"/> event.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>
+ /// Returns whether the login should proceed. False if some event handler canceled the request.
+ /// </returns>
+ protected virtual bool OnLoggingIn(IAuthenticationRequest request) {
+ Contract.Requires(request != null);
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ EventHandler<OpenIdEventArgs> loggingIn = this.LoggingIn;
+
+ OpenIdEventArgs args = new OpenIdEventArgs(request);
+ if (loggingIn != null) {
+ loggingIn(this, args);
+ }
+
+ return !args.Cancel;
+ }
+
+ /// <summary>
+ /// Fires the <see cref="Canceled"/> event.
+ /// </summary>
+ /// <param name="response">The response.</param>
+ protected virtual void OnCanceled(IAuthenticationResponse response) {
+ Contract.Requires(response != null);
+ Contract.Requires(response.Status == AuthenticationStatus.Canceled);
+ ErrorUtilities.VerifyArgumentNotNull(response, "response");
+ ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type.");
+
+ var canceled = this.Canceled;
+ if (canceled != null) {
+ canceled(this, new OpenIdEventArgs(response));
+ }
+ }
+
+ /// <summary>
+ /// Fires the <see cref="Failed"/> event.
+ /// </summary>
+ /// <param name="response">The response.</param>
+ protected virtual void OnFailed(IAuthenticationResponse response) {
+ Contract.Requires(response != null);
+ Contract.Requires(response.Status == AuthenticationStatus.Failed);
+ ErrorUtilities.VerifyArgumentNotNull(response, "response");
+ ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type.");
+
+ var failed = this.Failed;
+ if (failed != null) {
+ failed(this, new OpenIdEventArgs(response));
+ }
+ }
+
+ /// <summary>
+ /// Creates the relying party instance used to generate authentication requests.
+ /// </summary>
+ /// <returns>The instantiated relying party.</returns>
+ protected virtual OpenIdRelyingParty CreateRelyingParty() {
+ IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore);
+ var rp = new OpenIdRelyingParty(store);
+
+ // Only set RequireSsl to true, as we don't want to override
+ // a .config setting of true with false.
+ if (this.RequireSsl) {
+ rp.SecuritySettings.RequireSsl = true;
+ }
+
+ return rp;
+ }
+
+ /// <summary>
+ /// Detects whether a popup window should be used to show the Provider's UI
+ /// and applies the UI extension to the request when appropriate.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>
+ /// <c>true</c> if a popup should be used; <c>false</c> otherwise.
+ /// </returns>
+ private bool IsPopupAppropriate(IAuthenticationRequest request) {
+ Contract.Requires(request != null);
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ return this.Popup == PopupBehavior.Always || request.Provider.IsExtensionSupported<UIRequest>();
+ }
+
+ /// <summary>
+ /// Wires the return page to immediately display a popup window with the Provider in it.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ private void ScriptPopupWindow(IAuthenticationRequest request) {
+ Contract.Requires(request != null);
+ Contract.Requires(this.RelyingParty != null);
+
+ request.AddCallbackArguments(UIPopupCallbackKey, "1");
+
+ StringBuilder startupScript = new StringBuilder();
+
+ // Add a callback function that the popup window can call on this, the
+ // parent window, to pass back the authentication result.
+ startupScript.AppendLine("window.dnoa_internal = new Object();");
+ startupScript.AppendLine("window.dnoa_internal.processAuthorizationResult = function(uri) { window.location = uri; };");
+
+ // Open the popup window.
+ startupScript.AppendFormat(
+ @"var openidPopup = {0}",
+ UIUtilities.GetWindowPopupScript(this.RelyingParty, request, "openidPopup"));
+
+ this.Page.ClientScript.RegisterStartupScript(this.GetType(), "loginPopup", startupScript.ToString(), true);
+ }
+
+ /// <summary>
+ /// Wires the popup window to close itself and pass the authentication result to the parent window.
+ /// </summary>
+ private void ScriptClosingPopup() {
+ StringBuilder startupScript = new StringBuilder();
+ startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL + '&" + UIPopupCallbackParentKey + "=1');");
+ startupScript.AppendLine("window.close();");
+
+ this.Page.ClientScript.RegisterStartupScript(this.GetType(), "loginPopupClose", startupScript.ToString(), true);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs
new file mode 100644
index 0000000..bd45842
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="WellKnownProviders.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ /// <summary>
+ /// Common OpenID Provider Identifiers.
+ /// </summary>
+ public sealed class WellKnownProviders {
+ /// <summary>
+ /// The Yahoo OP Identifier.
+ /// </summary>
+ public static readonly Identifier Yahoo = "https://me.yahoo.com/";
+
+ /// <summary>
+ /// The Google OP Identifier.
+ /// </summary>
+ public static readonly Identifier Google = "https://www.google.com/accounts/o8/id";
+
+ /// <summary>
+ /// The MyOpenID OP Identifier.
+ /// </summary>
+ public static readonly Identifier MyOpenId = "https://www.myopenid.com/";
+
+ /// <summary>
+ /// Prevents a default instance of the <see cref="WellKnownProviders"/> class from being created.
+ /// </summary>
+ private WellKnownProviders() {
+ }
+ }
+}